/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview Base for all o3d classes implemented in javscript.
 *    Include just this file in a script tag and it will include all other
 *    source files needed by o3d.
 *    For more information about o3d see
 *    http://code.google.com/p/o3d.
 */

/**
 * A namespace for all the o3d classes.
 * @namespace
 */
var o3d = o3d || {};

/**
 * Define this because the Google internal JSCompiler needs goog.typedef below.
 */
var goog = goog || {};

/**
 * A macro for defining composite types.
 *
 * By assigning goog.typedef to a name, this tells Google internal JSCompiler
 * that this is not the name of a class, but rather it's the name of a composite
 * type.
 *
 * For example,
 * /** @type {Array|NodeList} / goog.ArrayLike = goog.typedef;
 * will tell JSCompiler to replace all appearances of goog.ArrayLike in type
 * definitions with the union of Array and NodeList.
 *
 * Does nothing in uncompiled code.
 */
goog.typedef = true;

/**
 * Reference to the global context.  In most cases this will be 'window'.
 */
o3d.global = this;

/**
 * Path for included scripts.
 * @type {string}
 */
o3d.basePath = '';

/**
 * Some javascripts don't support __defineGetter__ or __defineSetter__
 * so we define some here so at least we don't get compile errors.
 * We expect the initialzation code will check and complain. This stubs
 * are just here to make sure we can actually get to the initialization code.
 */
if (!Object.prototype.__defineSetter__) {
  Object.prototype.__defineSetter__ = function() {}
  Object.prototype.__defineGetter__ = function() {}
}

/**
 * Tries to detect the base path of the base.js script that
 * bootstraps the o3d libraries.
 * @private
 */
o3d.findBasePath_ = function() {
  var doc = o3d.global.document;
  if (typeof doc == 'undefined') {
    return;
  }
  if (o3d.global.BASE_PATH) {
    o3d.basePath = o3d.global.BASE_PATH;
    return;
  } else {
    // HACK to hide compiler warnings :(
    o3d.global.BASE_PATH = null;
  }
  var scripts = doc.getElementsByTagName('script');
  for (var script, i = 0; script = scripts[i]; i++) {
    var src = script.src;
    var l = src.length;
    var s = 'o3d-webgl/base.js';
    var sl = s.length;
    if (src.substr(l - sl) == s) {
      o3d.basePath = src.substr(0, l - sl) + 'o3d-webgl/';
      return;
    }
  }
};

/**
 * Writes a script tag for the given o3d source file name
 * to the document.  (Must be called at execution time.)
 * @param {string} src The full path to the source file.
 * @private
 */
o3d.writeScriptTag_ = function(src) {
  var doc = o3d.global.document;
  if (typeof doc != 'undefined') {
    doc.write('<script type="text/javascript" src="' +
              src + '"></' + 'script>');
  }
};

/**
 * Filters any "o3d." prefix from the given type name.
 * @param {string} type_name The type name to filter.
 * @return {string} Filtered type name.
 * @private
 */
o3d.filterTypeName_ = function(type_name) {
  if (type_name.length >= 4 && type_name.substr(0, 4) == 'o3d.') {
    type_name = type_name.substr(4);
  }
  return type_name;
};

/**
 * Includes the file indicated by the rule by adding a script tag.
 * @param {string} rule Rule to include, in the form o3d.package.part.
 */
o3d.include = function(rule) {
  var parts = rule.split('.');
  var path = parts[parts.length - 1] + '.js';
  o3d.writeScriptTag_(o3d.basePath + path);
};


/**
 * Makes one class inherit from another.  Adds the member variables superClass
 * and superClassName to the prototype of the sub class.
 * @param {string} subClass Class that wants to inherit.
 * @param {string} superClass Class to inherit from.
 */
o3d.inherit = function(subClassName, superClassName) {
  var superClass = o3d.global.o3d[superClassName];
  var subClass = o3d.global.o3d[subClassName];

  if (!superClass)
    throw ('Invalid superclass: ' + superClassName);
  if (!subClass)
    throw ('Invalid subclass: ' + subClassName);

  subClass.prototype = new superClass;
  subClass.prototype.superClassName = superClassName;
  subClass.prototype.superClass = superClass;
  subClass.prototype.className = subClassName;
};


/**
 * Utility function to remove an object from an array.
 * @param {!Array} array The array.
 * @param {Object} object The thing to be removed.
 */
o3d.removeFromArray = function(array, object) {
  var i = array.indexOf(object);
  if (i >= 0) {
    array.splice(i, 1);
  }
};


/**
 * Determine whether a value is an array. Do not use instanceof because that
 * will not work for V8 arrays (the browser thinks they are Objects).
 * @param {*} value A value.
 * @return {boolean} Whether the value is an array.
 */
o3d.isArray_ = function(value) {
  var valueAsObject = /** @type {!Object} **/ (value);
  return typeof(value) === 'object' && value !== null &&
      'length' in valueAsObject && 'splice' in valueAsObject;
};


/**
 * Utility function to clone an object.
 *
 * @param {Object} object The object to clone.
 * @return {Object} A clone of that object.
 */
o3d.clone = function(object) {
  var result = o3d.isArray_(object) ? [] : {};
  for (var name in object) {
    var property = object[name];
    if (typeof property == 'Object') {
      result[name] = o3d.clone(property);
    } else {
      result[name] = property;
    }
  }
  return result;
};


/**
 * If an o3d function has not been implemented in javascript yet, it should
 * call this function to throw an error because it's better than doing
 * nothing.
 */
o3d.notImplemented = function() {
  debugger;
  throw 'Not implemented.';
};


// First find the path to the directory where all o3d-webgl sources live.
o3d.findBasePath_();


// Unlike o3djs, we include all o3d-webgl files at once, this way, an o3d
// developer converting to use these classes only has to include this
// javascript file.
/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * A ObjectBase is a base class that manages a set of components in a
 * Buffer of a specific type. ObjectBases are managed by Buffers and can
 * not be directly created. When a Buffer is destroyed or if a ObjectBase
 * is removed from a Buffer the ObjectBase's buffer property will be set
 * to null.
 */
o3d.ObjectBase = function() { };


/**
 * Used by isAClassName.
 * @type {string}
 */
o3d.ObjectBase.prototype.className = 'o3d.ObjectBase';

/**
 * @type {o3d.ObjectBase}
 */
o3d.ObjectBase.prototype.superClass = null;

/**
 * Traverses the current object's class and all its superclasses and
 * determines if any of them are of the given name.
 * @param {string} class_type_name The name of a class.
 * @return {boolean} Whether this is counts as a className.
 */
o3d.ObjectBase.prototype.isAClassName = function(class_type_name) {
  class_type_name = o3d.filterTypeName_(class_type_name);
  var object = this;
  while (object != undefined) {
    if (object.className == class_type_name) {
      return true;
    }
    object = object.superClass && object.superClass.prototype;
  }
  return false;
};


/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * Base class for all objects that are identifiable by a name.
 * @constructor
 */
o3d.NamedObjectBase = function() {
  o3d.ObjectBase.call(this);
};
o3d.inherit('NamedObjectBase', 'ObjectBase');


/**
 * The object's name.
 * @type {string}
 */
o3d.NamedObjectBase.prototype.name = "";


/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * Base class for all objects that can have their name set.
 * @constructor
 */
o3d.NamedObject = function() {
  o3d.NamedObjectBase.call(this);
};
o3d.inherit('NamedObject', 'NamedObjectBase');


/**
 * The object's name.
 *
 * Setting this has no meaning to O3D, but is useful for debugging and for
 * the functions Client.getObjects, Pack.getObject,
 * RenderNode.getRenderNodesByNameInTree and
 * RenderNode.getTransformsByNameInTree
 * which search for objects by name.
 * @type {string}
 */
o3d.NamedObject.prototype.name = '';


/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * A ParamObject is the base class for all objects that can have Params.
 * Defines methods to add and remove params, search for params, etc.
 * @constructor
 */
o3d.ParamObject = function() {
  o3d.NamedObject.call(this);
  this.params_ = {};
};
o3d.inherit('ParamObject', 'NamedObject');

o3d.ParamObject.prototype.__defineGetter__('params',
    function() {
      var paramList = [];
      for (name in this.params_) {
        paramList.push(this.params_[name]);
      }
      return paramList;
    });

o3d.ParamObject.prototype.__defineSetter__('params', function() {});

o3d.ParamObject.O3D_PREFIX_ = 'o3d.';

/**
 * Creates a Param with the given name and type on the ParamObject.
 * Will fail if a param with the same name already exists.
 *
 * @param {string} param_name The name of the Param to be created.
 * @param {string} param_type_name The type of Param to create.
 *     Valid types are
 *     'ParamBoolean'
 *     'ParamBoundingBox'
 *     'ParamDrawContext'
 *     'ParamDrawList'
 *     'ParamEffect'
 *     'ParamFloat'
 *     'ParamFloat2'
 *     'ParamFloat3'
 *     'ParamFloat4'
 *     'ParamFunction'
 *     'ParamInteger'
 *     'ParamMaterial'
 *     'ParamMatrix4'
 *     'ParamParamArray'
 *     'ParamRenderSurface'
 *     'ParamRenderDepthStencilSurface'
 *     'ParamSampler'
 *     'ParamSkin'
 *     'ParamSteamBank'
 *     'ParamState'
 *     'ParamString'
 *     'ParamTexture'
 *     'ParamTransform'
 *     'ProjectionParamMatrix4'
 *     'ProjectionInverseParamMatrix4'
 *     'ProjectionTransposeParamMatrix4'
 *     'ProjectionInverseTransposeParamMatrix4'
 *     'ViewParamMatrix4'
 *     'ViewInverseParamMatrix4'
 *     'ViewTransposeParamMatrix4'
 *     'ViewInverseTransposeParamMatrix4'
 *     'ViewProjectionParamMatrix4'
 *     'ViewProjectionInverseParamMatrix4'
 *     'ViewProjectionTransposeParamMatrix4'
 *     'ViewProjectionInverseTransposeParamMatrix4'
 *     'WorldParamMatrix4'
 *     'WorldInverseParamMatrix4'
 *     'WorldTransposeParamMatrix4'
 *     'WorldInverseTransposeParamMatrix4'
 *     'WorldViewParamMatrix4'
 *     'WorldViewInverseParamMatrix4'
 *     'WorldViewTransposeParamMatrix4'
 *     'WorldViewInverseTransposeParamMatrix4'
 *     'WorldViewProjectionParamMatrix4'
 *     'WorldViewProjectionInverseParamMatrix4'
 *     'WorldViewProjectionTransposeParamMatrix4'
 *     'WorldViewProjectionInverseTransposeParamMatrix4'
 * @return {!o3d.Param}  The newly created Param or null on failure.
 */
o3d.ParamObject.prototype.createParam =
    function(param_name, param_type_name) {
  if (this.params_[param_name])
    return null;
  param_type_name = o3d.filterTypeName_(param_type_name);
  if (!o3d.global.o3d[param_type_name])
    throw ('Invalid param type name: ' + param_type_name);
  var param = new o3d.global.o3d[param_type_name];
  param.gl = this.gl;
  param.owner_ = this;
  param.name = param_name;
  this.params_[param_name] = param;
  return this.filterResult_(this.params_[param_name]);
};


/**
 * Searches by name for a Param defined in the object.
 *
 * @param {string} param_name Name to search for.
 * @return {!o3d.Param}  The Param with the given name, or null otherwise.
 */
o3d.ParamObject.prototype.getParam =
    function(param_name) {
  var result = this.params_[param_name];
  var o3d_name;
  if (!result) {
    // Try again with O3D prefix.
    o3d_name = o3d.ParamObject.O3D_PREFIX_ + param_name;
    result = this.params_[o3d_name];
  }

  if (!result) {
    // See if it's one of the params which needs to be created lazily.
    // If it is, initialize it with the current value in the object.
    var lazyParamMap = this.lazyParamMap_;
    if (lazyParamMap) {
      var name = param_name;
      var param_type = this.lazyParamMap_[name];
      if (!param_type) {
        name = o3d_name;
        param_type = this.lazyParamMap_[name];
      }
      if (param_type) {
        result = this.createParam(name, param_type);
      }
    }
  }

  return this.filterResult_(result);
};


/**
 * Removes a Param from a ParamObject.
 *
 * This function will fail if the param does not exist on this ParamObject
 * or if the param is unremovable.
 *
 * @param {!o3d.Param} param param to remove.
 * @return {boolean}  True if the param was removed.
 */
o3d.ParamObject.prototype.removeParam =
    function(param) {
  for (var i in this.params_) {
    if (this.params_[i] == param) {
      delete this.params_[i];
    }
  }
};


/**
 * Gets all the param on this param object.
 *
 * Each access to this field gets the entire list, so it is best to get it
 * just once. For example:
 *
 * var params = paramObject.params;
 * for (var i = 0; i < params.length; i++) {
 *   var param = params[i];
 * }
 *
 * Note that modifications to this array [e.g. push()] will not affect
 * the underlying ParamObject, while modifications to the array's members
 * will affect them.
 */
o3d.ParamObject.prototype.params_ = {};


/**
 * Copies all the params from a the given source_param_object to this param
 * object. Does not replace any currently existing params with the same name.
 *
 * @param {o3d.ParamObject} source_param_object param object to copy params
 *     from.
 */
o3d.ParamObject.prototype.copyParams =
    function(source_param_object) {
  for (name in source_param_object.params_) {
    var param = source_param_object.params_[name];
    this.createParam(name, param.className);
    this.getParam(name).value = param.value;
  }
};


/**
 * Filters results, turning 'undefined' into 'null'.
 * @private
 */
o3d.ParamObject.prototype.filterResult_= function(result) {
  return (result ? result : null);
};


/**
 * Sets up an o3d-scoped parameter against the given constructor
 * function of the given type for the given field.
 * @private
 */
o3d.ParamObject.setUpO3DParam_ = function(constructor,
                                          fieldName,
                                          paramType) {
  var o3dParamName = o3d.ParamObject.O3D_PREFIX_ + fieldName;

  // The lazyParamMap primarily handles the case where getParam is
  // called before the getter or setter below. It also simplifies the
  // code below since it can simply call getParam and the param will
  // be created on demand.
  var lazyParamMap = constructor.prototype.lazyParamMap_;
  if (!lazyParamMap) {
    lazyParamMap = {};
    constructor.prototype.lazyParamMap_ = lazyParamMap;
  }
  lazyParamMap[o3dParamName] = paramType;

  constructor.prototype.__defineGetter__(fieldName,
      function() {
        var param = this.getParam(o3dParamName);
        return param.value;
      });
  constructor.prototype.__defineSetter__(fieldName,
      function(v) {
        var param = this.getParam(o3dParamName);
        param.value = v;
      });
};

/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * A ParamArray is an object that holds an array of Params.
 * @constructor
 */
o3d.ParamArray = function() {
  o3d.NamedObject.call(this);
  this.params_ = [];
};
o3d.inherit('ParamArray', 'NamedObject');


/**
 * Creates a Param of the given type at the index requested. If a Param already
 * exists at that index the new param will be replace it. If the index is past
 * the end of the current array params of the requested type will be created to
 * fill out the array to the requested index.
 *
 * @param {number} index Index at which to create new param.
 * @param {string} param_type_name The type of Param to create. For a list of
 *     valid types see ParamObject.createParam
 * @return {!o3d.ParamArray}  The newly created Param or null if failure.
 */
o3d.ParamArray.prototype.createParam = function(index, param_type_name) {
  param_type_name = o3d.filterTypeName_(param_type_name);
  if (!o3d.global.o3d[param_type_name])
    throw ('Invalid param type name: ' + param_type_name);
  if (index >= this.params_.length) {
    this.resize(index + 1, param_type_name);
  } else {
    var param = new o3d.global.o3d[param_type_name];
    param.gl = this.gl;
    param.owner_ = this;
    this.params_[index] = param;
  }

  return this.filterResult_(this.params_[index]);
};


/**
 * Gets a Param by index.
 *
 * @param {number} index Index of Param to get.
 * @return {!o3d.Param}  The Param at index, or null if out of range.
 */
o3d.ParamArray.prototype.getParam = function(index) {
  var result = this.params_[index];
  return this.filterResult_(result);
};


/**
 * Removes a range of params. This shrinks the array and affects the indices of
 * later occurring items.
 *
 * @param {number} start_index Index of first param to remove.
 * @param {number} num_to_remove The number of params to remove starting at
 *     start_index.
 */
o3d.ParamArray.prototype.removeParams = function(start_index, num_to_remove) {
  var paramsNew = [];
  var j = 0;
  for (var i = 0; i < this.params_.length; i++) {
    if (i >= start_index && i < start_index + num_to_remove) {
      // Skip these to remove them.
    } else {
      paramsNew[j] = this.params_[i];
      j++;
    }
  }
  this.params_ = paramsNew;
};


/**
 * Resizes the array.
 *
 * @param {number} num_params The number of params to make the array.
 * @param {string} param_type_name The type of Param to create if resizing
 *     requires params to be created. For a list of valid types see
 *     ParamObject.createParam.
 */
o3d.ParamArray.prototype.resize = function(num_params, param_type_name) {
  param_type_name = o3d.filterTypeName_(param_type_name);
  if (!o3d.global.o3d[param_type_name])
    throw ('Invalid param type name: ' + param_type_name);

  for (var i = this.params_.length; i < num_params; i++) {
    var param = new o3d.global.o3d[param_type_name];
    param.gl = this.gl;
    param.owner_ = this;
    this.params_[i] = param;
  }
};

/**
 * The params stored in this ParamArray.
 *
 * @type {!Array.<!o3d.Param>}
 * @private
 */
o3d.ParamArray.prototype.params_ = [];

/**
 * Gets all the param on this param object.
 *
 * Each access to this field gets the entire list, so it is best to get it
 * just once. For example:
 *
 * var params = ParamArray.params;
 * for (var i = 0; i < params.length; i++) {
 *   var param = params[i];
 * }
 *
 * Note that modifications to this array [e.g. push()] will not affect
 * the underlying ParamArray, while modifications to the array's members
 * <b>will</b> affect them.
 *
 * @type {!Array.<!o3d.Param>}
 */
o3d.ParamArray.prototype.__defineGetter__('params',
    function() {
      var params = [];
      for (var i = 0; i < this.length; i++) {
        params[i] = this.params_[i];
      }
      return params;
    }
);


/**
 * Returns the number of parameters in this ParamArray.
 *
 * @type {number}
 */
o3d.ParamArray.prototype.__defineGetter__('length',
    function() {
      return this.params_.length;
    }
);


/**
 * Filters results, turning 'undefined' into 'null'.
 *
 * @param {*} result
 * @private
 */
o3d.ParamArray.prototype.filterResult_= function(result) {
  return (result ? result : null);
};
/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * Params store data defined name/value pairs on ParamObjects.
 * Each Param has a name, a type and a value that can be set and queried.
 * One of their uses is to hold "uniform constants" used to parameterize
 * shaders.  Params can be connected in a source/destination fashion such
 * that a target Param gets its value from the source param.
 * @constructor
 */
o3d.Param = function(param_type_name) {
  o3d.Param.prototype.output_connections = [];
  this.outputConnections = [];
}
o3d.inherit('Param', 'NamedObject');


/**
 * If true, this param will make sure its input param is up to date when
 * using it as a source. Default = true.
 *
 * This is for helping with Param cycles.
 *
 * If paramA gets its value from paramB and paramB gets its value from
 * paramA:
 * If you go paramA.value, paramB will evaluate then copy to paramA.
 * If you go paramB.value, paramA will evaluate then copy to paramB.
 * If you set paramB.updateInput = false, then:
 * If you go paramA.value, paramB will evaluate then copy to paramA.
 * If you go paramB.value, paramB just copy paramA. paramA will NOT evaluate
 * when paramB asks for its value.
 */
o3d.Param.prototype.update_input = true;

/**
 * @type {o3d.Param}
 */
o3d.Param.prototype.inputConnection = null;

/**
 * @type {Array.<!o3d.Param>}
 */
o3d.Param.prototype.outputConnections = [];


/**
 * @type {o3d.ParamObject}
 */
o3d.Param.prototype.owner_ = null;

/**
 * @type {Object} The value of the parameter.
 */
o3d.Param.prototype.value_ = null;

o3d.Param.prototype.__defineSetter__('value',
    function(v) {
      if (this.inputConnection) {
        throw ('Tried to set bound parameter.');
      } else {
        if (this.value_ != undefined && (
           typeof this.value_ != typeof v ||
           (this.value_.length_ !== undefined &&
            (this.value_.length_ != v.length)))) {
          this.gl.client.error_callback('Param type error.');
        }
        this.value_ = v;
      }
    }
);

o3d.Param.prototype.__defineGetter__('value',
    function() {
      if (this.inputConnection) {
        return this.inputConnection.value;
      } else {
        return this.value_;
      }
    }
);


/**
 * Directly binds two Param elements such that this parameter gets its value
 * from the source parameter.  The source must be compatible with this
 * parameter.
 *
 * @param {o3d.Param} source_param The parameter that the value originates
 *     from. Passing in null will unbind any parameter currently bound.
 * @return {boolean}  True if the bind succeeded.
 */
o3d.Param.prototype.bind =
    function(source_param) {
  source_param.outputConnections.push(this);
  this.inputConnection = source_param;
};


/**
 * Breaks any input connection coming into the Param.
 */
o3d.Param.prototype.unbindInput =
    function() {
  o3d.notImplemented();
};


/**
 * Breaks a specific param-bind output connection on this param.
 *
 * @param {o3d.Param} destination_param param to unbind.
 */
o3d.Param.prototype.unbindOutput =
    function(destination_param) {
  o3d.notImplemented();
};


/**
 * Breaks all param-bind output connections on this param.
 */
o3d.Param.prototype.unbindOutputs = function() {
  o3d.notImplemented();
};



/**
 * If true the param is read only. Its value can not be set nor can it be used
 * as the destination in a ParamBind.
 */
o3d.Param.prototype.read_only_ = false;


/**
 * @constructor
 */
o3d.ParamBoolean = function() {
  o3d.Param.call(this);
  this.value = false;
};
o3d.inherit('ParamBoolean', 'Param');


/**
 * @constructor
 */
o3d.ParamBoundingBox = function() {
  o3d.Param.call(this);
  this.value = null;
};
o3d.inherit('ParamBoundingBox', 'Param');

// ParamBoundingBox requires a specialized setter because it must
// accept arrays of arrays and convert them into BoundingBoxes. It
// seems that if we define a setter against this prototype we must
// also define a getter -- it is not inherited.
o3d.ParamBoundingBox.prototype.__defineSetter__('value',
    function(v) {
      if (this.inputConnection) {
        throw ('Tried to set bound parameter.');
      } else {
        if (!v) {
          v = new o3d.BoundingBox();
        } else if (v.length !== undefined) {
          if (v.length == 0) {
            v = new o3d.BoundingBox();
          } else if (v.length == 2) {
            for (var ii = 0; ii < 2; ++ii) {
              if (v[ii].length != 3) {
                throw ('Expected sub-array of length 3 at index ' + ii +
                       ', got ' + v[ii].length);
              }
            }
            v = new o3d.BoundingBox(v[0], v[1]);
          } else {
            throw 'Expected array of length 2';
          }
        }
        this.value_ = v;
      }
    }
);

o3d.ParamBoundingBox.prototype.__defineGetter__('value',
    function() {
      if (this.inputConnection) {
        return this.inputConnection.value;
      } else {
        return this.value_;
      }
    }
);

/**
 * @constructor
 */
o3d.ParamDrawContext = function() {
  o3d.Param.call(this);
  this.value = null;
};
o3d.inherit('ParamDrawContext', 'Param');


/**
 * @constructor
 */
o3d.ParamDrawList = function() {
  o3d.Param.call(this);
  this.value = null;
};
o3d.inherit('ParamDrawList', 'Param');


/**
 * @constructor
 */
o3d.ParamEffect = function() {
  o3d.Param.call(this);
  this.value = null;
};
o3d.inherit('ParamEffect', 'Param');


/**
 * @constructor
 */
o3d.ParamFloat = function() {
  o3d.Param.call(this);
  this.value = 0.0;
};
o3d.inherit('ParamFloat', 'Param');


/**
 * @constructor
 */
o3d.ParamFloat2 = function() {
  o3d.Param.call(this);
  this.value = [0.0, 0.0];
};
o3d.inherit('ParamFloat2', 'Param');


/**
 * @constructor
 */
o3d.ParamFloat3 = function() {
  o3d.Param.call(this);
  this.value = [0.0, 0.0, 0.0];
};
o3d.inherit('ParamFloat3', 'Param');


/**
 * @constructor
 */
o3d.ParamFloat4 = function() {
  o3d.Param.call(this);
  this.value = [0.0, 0.0, 0.0, 0.0];
};
o3d.inherit('ParamFloat4', 'Param');


/**
 * @constructor
 */
o3d.ParamFunction = function() {
  o3d.Param.call(this);
  this.value = null;
};
o3d.inherit('ParamFunction', 'Param');


/**
 * @constructor
 */
o3d.ParamInteger = function() {
  o3d.Param.call(this);
  this.value = 0;
};
o3d.inherit('ParamInteger', 'Param');


/**
 * @constructor
 */
o3d.ParamMaterial = function() {
  o3d.Param.call(this);
  this.value = null;
};
o3d.inherit('ParamMaterial', 'Param');


/**
 * @constructor
 */
o3d.ParamMatrix4 = function() {
  o3d.Param.call(this);
  this.value = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];
};
o3d.inherit('ParamMatrix4', 'Param');


/**
 * @constructor
 */
o3d.ParamParamArray = function() {
  o3d.Param.call(this);
  this.value = null;
};
o3d.inherit('ParamParamArray', 'Param');

/**
 * Acts like ParamParamArray, but asks its owner object to update the array
 * contents every time its value is queried.
 *
 * @constructor
 * @extends {o3d.ParamParamArray}
 */
o3d.ParamParamArrayOutput = function() {
  o3d.ParamParamArray.call(this);
};
o3d.inherit('ParamParamArrayOutput', 'ParamParamArray');
o3d.ParamParamArrayOutput.prototype.__defineGetter__("value",
    function() {
      this.owner_.updateOutputs(this);
      return this.value_;
    }
);
o3d.ParamParamArrayOutput.prototype.__defineSetter__("value",
    function(value) {
      // Creating a new array is fine.
      this.value_ = value;
    }
);

/**
 * @constructor
 */
o3d.ParamRenderSurface = function() {
  o3d.Param.call(this);
  this.value = null;
};
o3d.inherit('ParamRenderSurface', 'Param');


/**
 * @constructor
 */
o3d.ParamRenderDepthStencilSurface = function() {
  o3d.Param.call(this);
  this.value = null;
};
o3d.inherit('ParamRenderDepthStencilSurface', 'Param');


/**
 * @constructor
 */
o3d.ParamSampler = function() {
  o3d.Param.call(this);
  this.value = null;
};
o3d.inherit('ParamSampler', 'Param');


/**
 * @constructor
 */
o3d.ParamSkin = function() {
  o3d.Param.call(this);
  this.value = null;
};
o3d.inherit('ParamSkin', 'Param');


/**
 * @constructor
 */
o3d.ParamSteamBank = function() {
  o3d.Param.call(this);
  this.value = null;
};
o3d.inherit('ParamSteamBank', 'Param');


/**
 * @constructor
 */
o3d.ParamState = function() {
  o3d.Param.call(this);
  this.value = null;
};
o3d.inherit('ParamState', 'Param');


/**
 * @constructor
 */
o3d.ParamStreamBank = function() {
  o3d.Param.call(this);
  this.value = null;
};
o3d.inherit('ParamStreamBank', 'Param');


/**
 * @constructor
 */
o3d.ParamString = function() {
  o3d.Param.call(this);
  this.value = null;
};
o3d.inherit('ParamString', 'Param');


/**
 * @constructor
 */
o3d.ParamTexture = function() {
  o3d.Param.call(this);
  this.value = null;
};
o3d.inherit('ParamTexture', 'Param');


/**
 * @constructor
 */
o3d.ParamTransform = function() {
  o3d.Param.call(this);
  this.value = null;
};
o3d.inherit('ParamTransform', 'Param');


/**
 * @constructor
 */
o3d.ParamVertexBufferStream = function() {
  o3d.Param.call(this);
  this.stream = null;
};
o3d.inherit('ParamVertexBufferStream', 'Param');

/**
 * Base class for the types of matrix4 params that compute their own
 * value when asked (ProjectionParamMatrix4 etc).
 * @constructor
 */
o3d.CompositionParamMatrix4 = function() {
  o3d.ParamMatrix4.call(this);
  this.matrix_names_ = [];
};
o3d.inherit('CompositionParamMatrix4', 'ParamMatrix4');

/**
 * The array of names of matrix params for the matrices that are to be
 * composed to get the value.
 * @type {Array.<o3d.ParamMatrix4>}
 */
o3d.CompositionParamMatrix4.prototype.matrix_names_ = [];

/**
 * Whether the inverse is taken right before returning the value.
 * @type {Array.<o3d.ParamMatrix4>}
 */
o3d.CompositionParamMatrix4.prototype.inverse_ = false;


/**
 * Whether the inverse is taken right before returning the value.
 * @type {Array.<o3d.ParamMatrix4>}
 */
o3d.CompositionParamMatrix4.prototype.transpose_ = false;

o3d.CompositionParamMatrix4.prototype.__defineGetter__('value',
    // TODO(petersont): Cache the result if it hasn't changed.
    function() {
      var product = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];
      for (var i = 0; i < this.matrix_names_.length; ++i) {
        o3d.Transform.compose(product, o3d.Param.SAS[this.matrix_names_[i]]);
      }
      if (this.inverse_) {
        o3d.Transform.inverse(product);
      }
      if (this.transpose_) {
        o3d.Transform.transpose(product);
      }
      return product;
    }
);

o3d.CompositionParamMatrix4.prototype.__defineSetter__('value',
    function(value) { }
);


/**
 * @constructor
 */
o3d.ProjectionParamMatrix4 = function() {
  o3d.CompositionParamMatrix4.call(this);
  this.matrix_names_ = ['projection'];
};
o3d.inherit('ProjectionParamMatrix4', 'CompositionParamMatrix4');


/**
 * @constructor
 */
o3d.ProjectionInverseParamMatrix4 = function() {
  o3d.CompositionParamMatrix4.call(this);
  this.matrix_names_ = ['projection'];
  this.inverse_ = true;
};
o3d.inherit('ProjectionInverseParamMatrix4', 'CompositionParamMatrix4');


/**
 * @constructor
 */
o3d.ProjectionTransposeParamMatrix4 = function() {
  o3d.CompositionParamMatrix4.call(this);
  this.matrix_names_ = ['projection'];
  this.transpose_ = true;
};
o3d.inherit('ProjectionTransposeParamMatrix4', 'CompositionParamMatrix4');


/**
 * @constructor
 */
o3d.ProjectionInverseTransposeParamMatrix4 = function() {
  o3d.CompositionParamMatrix4.call(this);
  this.matrix_names_ = ['projection'];
  this.inverse_ = true;
  this.transpose_ = true;
};
o3d.inherit('ProjectionInverseTransposeParamMatrix4',
    'CompositionParamMatrix4');


/**
 * @constructor
 */
o3d.ViewParamMatrix4 = function() {
  o3d.CompositionParamMatrix4.call(this);
  this.matrix_names_ = ['view'];
};
o3d.inherit('ViewParamMatrix4', 'CompositionParamMatrix4');


/**
 * @constructor
 */
o3d.ViewInverseParamMatrix4 = function() {
  o3d.CompositionParamMatrix4.call(this);
  this.matrix_names_ = ['view'];
  this.inverse_ = true;
};
o3d.inherit('ViewInverseParamMatrix4', 'CompositionParamMatrix4');


/**
 * @constructor
 */
o3d.ViewTransposeParamMatrix4 = function() {
  o3d.CompositionParamMatrix4.call(this);
  this.matrix_names_ = ['view'];
  this.transpose_ = true;
};
o3d.inherit('ViewTransposeParamMatrix4', 'CompositionParamMatrix4');


/**
 * @constructor
 */
o3d.ViewInverseTransposeParamMatrix4 = function() {
  o3d.CompositionParamMatrix4.call(this);
  this.matrix_names_ = ['view'];
  this.inverse_ = true;
  this.transpose_ = true;
};
o3d.inherit('ViewInverseTransposeParamMatrix4', 'CompositionParamMatrix4');


/**
 * @constructor
 */
o3d.ViewProjectionParamMatrix4 = function() {
  o3d.CompositionParamMatrix4.call(this);
  this.matrix_names_ = ['viewProjection'];
};
o3d.inherit('ViewProjectionParamMatrix4', 'CompositionParamMatrix4');


/**
 * @constructor
 */
o3d.ViewProjectionInverseParamMatrix4 = function() {
  o3d.CompositionParamMatrix4.call(this);
  this.matrix_names_ = ['viewProjection'];
  this.inverse_ = true;
};
o3d.inherit('ViewProjectionInverseParamMatrix4', 'CompositionParamMatrix4');


/**
 * @constructor
 */
o3d.ViewProjectionTransposeParamMatrix4 = function() {
  o3d.CompositionParamMatrix4.call(this);
  this.matrix_names_ = ['viewProjection'];
  this.transpose_ = true;
};
o3d.inherit('ViewProjectionTransposeParamMatrix4', 'CompositionParamMatrix4');


/**
 * @constructor
 */
o3d.ViewProjectionInverseTransposeParamMatrix4 = function() {
  o3d.CompositionParamMatrix4.call(this);
  this.matrix_names_ = ['viewProjection'];
  this.inverse_ = true;
  this.transpose_ = true;
};
o3d.inherit('ViewProjectionInverseTransposeParamMatrix4',
    'CompositionParamMatrix4');


/**
 * @constructor
 */
o3d.WorldParamMatrix4 = function() {
  o3d.CompositionParamMatrix4.call(this);
  this.matrix_names_ = ['world'];
};
o3d.inherit('WorldParamMatrix4', 'CompositionParamMatrix4');


/**
 * @constructor
 */
o3d.WorldInverseParamMatrix4 = function() {
  o3d.CompositionParamMatrix4.call(this);
  this.matrix_names_ = ['world'];
  this.inverse_ = true;
};
o3d.inherit('WorldInverseParamMatrix4', 'CompositionParamMatrix4');


/**
 * @constructor
 */
o3d.WorldTransposeParamMatrix4 = function() {
  o3d.CompositionParamMatrix4.call(this);
  this.matrix_names_ = ['world'];
  this.transpose_ = true;
};
o3d.inherit('WorldTransposeParamMatrix4', 'CompositionParamMatrix4');


/**
 * @constructor
 */
o3d.WorldInverseTransposeParamMatrix4 = function() {
  o3d.CompositionParamMatrix4.call(this);
  this.matrix_names_ = ['world'];
  this.inverse_ = true;
  this.transpose_ = true;
};
o3d.inherit('WorldInverseTransposeParamMatrix4', 'CompositionParamMatrix4');


/**
 * @constructor
 */
o3d.WorldViewParamMatrix4 = function() {
  o3d.CompositionParamMatrix4.call(this);
  this.matrix_names_ = ['view', 'world'];
};
o3d.inherit('WorldViewParamMatrix4', 'CompositionParamMatrix4');


/**
 * @constructor
 */
o3d.WorldViewInverseParamMatrix4 = function() {
  o3d.CompositionParamMatrix4.call(this);
  this.matrix_names_ = ['view', 'world'];
  this.inverse_ = true;
};
o3d.inherit('WorldViewInverseParamMatrix4',
    'CompositionParamMatrix4');


/**
 * @constructor
 */
o3d.WorldViewTransposeParamMatrix4 = function() {
  o3d.CompositionParamMatrix4.call(this);
  this.matrix_names_ = ['view', 'world'];
  this.transpose_ = true;
};
o3d.inherit('WorldViewTransposeParamMatrix4',
    'CompositionParamMatrix4');


/**
 * @constructor
 */
o3d.WorldViewInverseTransposeParamMatrix4 = function() {
  o3d.CompositionParamMatrix4.call(this);
  this.matrix_names_ = ['view', 'world'];
  this.inverse_ = true;
  this.transpose_ = true;
};
o3d.inherit('WorldViewInverseTransposeParamMatrix4',
    'CompositionParamMatrix4');


/**
 * @constructor
 */
o3d.WorldViewProjectionParamMatrix4 = function() {
  o3d.CompositionParamMatrix4.call(this);
  this.matrix_names_ = ['worldViewProjection'];
};
o3d.inherit('WorldViewProjectionParamMatrix4',
    'CompositionParamMatrix4');


/**
 * @constructor
 */
o3d.WorldViewProjectionInverseParamMatrix4 = function() {
  o3d.CompositionParamMatrix4.call(this);
  this.matrix_names_ = ['worldViewProjection'];
  this.inverse_ = true;
};
o3d.inherit('WorldViewProjectionInverseParamMatrix4',
    'CompositionParamMatrix4');


/**
 * @constructor
 */
o3d.WorldViewProjectionTransposeParamMatrix4 = function() {
  o3d.CompositionParamMatrix4.call(this);
  this.matrix_names_ = ['worldViewProjection'];
  this.transpose_ = true;
};
o3d.inherit('WorldViewProjectionTransposeParamMatrix4',
    'CompositionParamMatrix4');


/**
 * @constructor
 */
o3d.WorldViewProjectionInverseTransposeParamMatrix4 = function() {
  o3d.CompositionParamMatrix4.call(this);
  this.matrix_names_ = ['worldViewProjection'];
  this.inverse_ = true;
  this.transpose_ = true;
};
o3d.inherit('WorldViewProjectionInverseTransposeParamMatrix4',
    'CompositionParamMatrix4');


/**
 * Called to specify the value of a uniform variable.
 * @param {WebGLContext} gl The current context.
 * @param {WebGLUniformLocation} location The location to which to apply.
 */
o3d.ParamInteger.prototype.applyToLocation = function(gl, location) {
  gl.uniform1i(location, this.value);
};

/**
 * Called to specify the value of a uniform variable.
 * @param {WebGLContext} gl The current context.
 * @param {WebGLUniformLocation} location The location to which to apply.
 */
o3d.ParamBoolean.prototype.applyToLocation = function(gl, location) {
  gl.uniform1i(location, this.value);
};

/**
 * Called to specify the value of a uniform variable.
 * @param {WebGLContext} gl The current context.
 * @param {WebGLUniformLocation} location The location to which to apply.
 */
o3d.ParamFloat.prototype.applyToLocation = function(gl, location) {
  gl.uniform1f(location, this.value);
};

/**
 * Called to specify the value of a uniform variable.
 * @param {WebGLContext} gl The current context.
 * @param {WebGLUniformLocation} location The location to which to apply.
 */
o3d.ParamFloat2.prototype.applyToLocation = function(gl, location) {
  gl.uniform2fv(location, this.value);
};

/**
 * Called to specify the value of a uniform variable.
 * @param {WebGLContext} gl The current context.
 * @param {WebGLUniformLocation} location The location to which to apply.
 */
o3d.ParamFloat3.prototype.applyToLocation = function(gl, location) {
  gl.uniform3fv(location, this.value);
};

/**
 * Called to specify the value of a uniform variable.
 * @param {WebGLContext} gl The current context.
 * @param {WebGLUniformLocation} location The location to which to apply.
 */
o3d.ParamFloat4.prototype.applyToLocation = function(gl, location) {
  gl.uniform4fv(location, this.value);
};

/**
 * Called to specify the value of a uniform variable.
 * @param {WebGLContext} gl The current context.
 * @param {WebGLUniformLocation} location The location to which to apply.
 */
o3d.ParamMatrix4.prototype.applyToLocation = function(gl, location) {
  gl.uniformMatrix4fv(location,
                      false,
                      o3d.Transform.flattenMatrix4(this.value));
};

/**
 * Called to specify the values of a uniform array.
 * @param {WebGLContext} gl The current context.
 * @param {!Array.<!WebGLUniformLocation>} locationArray An array of locations
 *    to which to apply the values.
 */
o3d.ParamParamArray.prototype.applyToLocations = function(gl, locationArray) {
  var computedValue = this.value;
  if (locationArray.length != computedValue.length) {
    gl.client.error_callback(
        'Invalid uniform param array: incorrect number of elements.');
  }
  for (var i = 0; i < computedValue.length; i++) {
    // Cannot have a ParamArray of ParamArrays, so safe to call applyToLocation
    computedValue.getParam(i).applyToLocation(gl, locationArray[i]);
  }
};

/**
 * A counter to ensure each texture sampler gets a unqiue id.
 * @private
 */
o3d.Param.texture_index_ = 0;

/**
 * Called to specify the value of a uniform variable.
 * @param {WebGLContext} gl The current context.
 * @param {WebGLUniformLocation} location The location to which to apply.
 * @param {boolean} opt_isCube Optional boolean indicating whether the Sampler
 *     connects to a samplerCube type uniform.  If set to true, and there is an
 *     error, we use the error cube map.
 */
o3d.ParamSampler.prototype.applyToLocation =
    function(gl, location, opt_isCube) {
  // When before the effect object assigns values to parameters,
  // it sets this variable to 0.
  var i = o3d.Param.texture_index_;
  gl.activeTexture(gl.TEXTURE0 + i);

  var value = null;
  var target = 0;
  var sampler = null;

  if (this.value) {
    sampler = this.value;
  } else {
    o3d.Sampler.defaultSampler_.gl = gl;
    sampler = o3d.Sampler.defaultSampler_;
    if (gl.client.reportErrors_()) {
      gl.client.error_callback("Missing Sampler for ParamSampler " + this.name);
    }
  }

  sampler.bindAndSetParameters_(opt_isCube);
  gl.uniform1i(location, i);
  o3d.Param.texture_index_++;
};

/**
 * A default ParamSampler to be used if client does not assign one.
 *
 * @type {!o3d.ParamSampler}
 * @private
 */
o3d.ParamSampler.defaultParamSampler_ = new o3d.ParamSampler();

/**
 * Object to compute all combinations of world/view/projection
 * inverse/transpose matrices and provide them as parameters.
 *
 * @type {o3d.ParamObject}
 */
o3d.Param.SAS = new o3d.ParamObject;

/**
 * A map linking the names of SAS parameters to their standard matrix parameter
 * types.
 * @private
 */
o3d.Param.sasTypes_ = {
  'world': 'WorldParamMatrix4',
  'view': 'ViewParamMatrix4',
  'projection': 'ProjectionParamMatrix4',
  'worldView': 'WorldViewParamMatrix4',
  'viewProjection': 'ViewProjectionParamMatrix4',
  'worldViewProjection': 'WorldViewProjectionParamMatrix4',
  'worldInverse': 'WorldInverseParamMatrix4',
  'viewInverse': 'ViewInverseParamMatrix4',
  'projectionInverse': 'ProjectionInverseParamMatrix4',
  'worldViewInverse': 'WorldViewInverseParamMatrix4',
  'viewProjectionInverse': 'ViewProjectionInverseParamMatrix4',
  'worldViewProjectionInverse': 'WorldViewProjectionInverseParamMatrix4',
  'worldTranspose': 'WorldTransposeParamMatrix4',
  'viewTranspose': 'ViewTransposeParamMatrix4',
  'projectionTranspose': 'ProjectionTransposeParamMatrix4',
  'worldViewTranspose': 'WorldViewTransposeParamMatrix4',
  'viewProjectionTranspose': 'ViewProjectionTransposeParamMatrix4',
  'worldViewProjectionTranspose': 'WorldViewProjectionTransposeParamMatrix4',
  'worldInverseTranspose': 'WorldInverseTransposeParamMatrix4',
  'viewInverseTranspose': 'ViewInverseTransposeParamMatrix4',
  'projectionInverseTranspose': 'ProjectionInverseTransposeParamMatrix4',
  'worldViewInverseTranspose': 'WorldViewInverseTransposeParamMatrix4',
  'viewProjectionInverseTranspose':
      'ViewProjectionInverseTransposeParamMatrix4',
  'worldViewProjectionInverseTranspose':
      'WorldViewProjectionInverseTransposeParamMatrix4'
};

for (name in o3d.Param.sasTypes_) {
  o3d.Param.SAS.createParam(name, o3d.Param.sasTypes_[name]);
}

/**
 * Sets the base world matrix that gets use to compute all other products for
 * SAS parameters.
 */
o3d.Param.SAS.setWorld = function(world) {
  this['world'] = world;
};

/**
 * Sets the base world matrix that gets use to compute all other products for
 * SAS parameters.
 */
o3d.Param.SAS.setView = function(view) {
  this['view'] = view;
};

/**
 * Sets the base world matrix that gets use to compute all other products for
 * SAS parameters.
 */
o3d.Param.SAS.setProjection = function(projection) {
  this['projection'] = projection;
};

/**
 * Sets the viewProjection matrix.
 */
o3d.Param.SAS.setViewProjection = function(viewProjection) {
  this['viewProjection'] = viewProjection;
};

/**
 * Sets the worldViewProjection matrix.
 */
o3d.Param.SAS.setWorldViewProjection = function(worldViewProjection) {
  this['worldViewProjection'] = worldViewProjection;
};
/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * An Event object contains information describing a JavaScript event; it's used
 * as an argument to event handlers triggered by the plugin.
 * @constructor
 */
o3d.Event = function() {
  o3d.ObjectBase.call(this);
};
o3d.inherit('Event', 'ObjectBase');


/**
 * @type {number}
 */
o3d.Event.Type = goog.typedef;

/**
 * String identifiers for JavaScript events.
 *  type
 *  invalid
 *  click
 *  dblclick
 *  mousedown
 *  mousemove
 *  mouseup
 *  wheel
 *  keydown
 *  keypress
 *  keyup
 *  resize
 */
o3d.Event.TYPE_INVALID = 0;
o3d.Event.TYPE_CLICK = 1;
o3d.Event.TYPE_DBLCLICK = 2;
o3d.Event.TYPE_MOUSEDOWN = 3;
o3d.Event.TYPE_MOUSEMOVE = 4;
o3d.Event.TYPE_MOUSEUP = 5;
o3d.Event.TYPE_WHEEL = 6;
o3d.Event.TYPE_KEYDOWN = 7;
o3d.Event.TYPE_KEYPRESS = 8;
o3d.Event.TYPE_KEYUP = 9;
o3d.Event.TYPE_RESIZE = 10;

/**
 * The type of event this object represents.
 * @type {o3d.Event.Type}
 */
o3d.Event.prototype.type = o3d.Event.TYPE_INVALID;


/**
 * @type {number}
 */
o3d.Event.Button = goog.typedef;

/**
 * Constants used to identify mouse buttons.
 */
o3d.Event.BUTTON_LEFT = 0;
o3d.Event.BUTTON_MIDDLE = 1;
o3d.Event.BUTTON_RIGHT = 2;
o3d.Event.BUTTON_4 = 3;
o3d.Event.BUTTON_5 = 4;



/**
 * Which mouse button caused the event, in the case of mousedown, mouseup,
 * click, and dblclick events.  This uses the values in enum Button.
 */
o3d.Event.prototype.button = o3d.Event.BUTTON_LEFT;



/**
 * Whether the ctrl key was pressed at the time of the event.
 * @type {boolean}
 */
o3d.Event.prototype.ctrl_key = false;



/**
 * Whether the alt [option, on OSX] key was pressed at the time of the event.
 * @type {boolean}
 */
o3d.Event.prototype.alt_key = false;



/**
 * Whether the shift key was pressed at the time of the event.
 * @type {boolean}
 */
o3d.Event.prototype.shift_key = false;



/**
 * Whether the meta [command, on OSX] key was pressed at the time of the event.
 * @type {boolean}
 */
o3d.Event.prototype.meta_key = false;



/**
 * The key code of the key pressed or released.
 * @type {number}
 */
o3d.Event.prototype.key_code = 0;



/**
 * The character created by a keypress event.
 * @type {number}
 */
o3d.Event.prototype.char_code = 0;



/**
 * The x-coordinate in pixels from the left side of the plugin or fullscreen
 * display region.
 * @type {number}
 */
o3d.Event.prototype.x = 0;



/**
 * The y-coordinate in pixels from the top of the plugin or fullscreen
 * display region.
 * @type {number}
 */
o3d.Event.prototype.y = 0;



/**
 * The x-coordinate in pixels from the left side of the screen.
 * @type {number}
 */
o3d.Event.prototype.screenX = 0;



/**
 * The y-coordinate in pixels from the top of the screen.
 * @type {number}
 */
o3d.Event.prototype.screenY = 0;



/**
 * The horizontal scroll offset for wheel events, in arbitrary units.
 * Positive values mean right; negative mean left.
 * @type {number}
 */
o3d.Event.prototype.deltaX = 0;



/**
 * The vertical scroll offset for wheel events, in arbitrary units.
 * Positive values mean up or away from the user; negative mean down or toward
 * the user.
 * @type {number}
 */
o3d.Event.prototype.deltaY = 0;



/**
 * The width in pixels of the plugin or fullscreen display region as a result
 * of this event.
 * @type {number}
 */
o3d.Event.prototype.width = 0;



/**
 * The height in pixels of the plugin or fullscreen display region as a result
 * of this event.
 * @type {number}
 */
o3d.Event.prototype.height = 0;



/**
 * Whether we're currently displaying in fullscreen mode.
 * @type {boolean}
 */
o3d.Event.prototype.fullscreen = false;



/**
 * An Event that gets sent to the render callback.
 * @constructor
 */
o3d.RenderEvent = function() {
  o3d.Event.call(this);
};
o3d.inherit('RenderEvent', 'Event');


/**
 * Time in seconds since the last time the client rendered.
 * @type {number}
 */
o3d.RenderEvent.prototype.elapsedTime = 0;


/**
 * An Event that gets sent to the render callback.
 * @constructor
 */
o3d.TickEvent = function() {
  o3d.Event.call(this);
};
o3d.inherit('RenderEvent', 'Event');


/**
 * Time in seconds since the last time the client rendered.
 * @type {number}
 */
o3d.TickEvent.prototype.elapsedTime = 0;

/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * A RawData object contains raw binary data which could contain
 * image, audio, text, or other information.
 *
 * var request =  g_pack.createArchiveRequest();
 *
 * request.onfileavailable = function(rawData) {
 *   var texture = g_pack.createTextureFromRawData(rawData, true);
 *   ...
 * };
 *
 * request.send();
 * @constructor
 */
o3d.RawData = function() {
  o3d.NamedObject.call(this);
};
o3d.inherit('RawData', 'NamedObject');


/**
 * Returns the raw data as a string. The data must be a valid utf-8 string
 * and the uri must end in .json, .txt, .xml, .ini or .csv
 * @type {string}
 */
o3d.RawData.prototype.stringValue = '';


/**
 * The data as an image if it is an image.
 * @type {Image}
 * @private
 */
o3d.RawData.prototype.image_ = null;


/**
 * The uri of the RawData.
 * @type {string}
 */
o3d.RawData.prototype.uri = '';



/**
 * The length in bytes of the RawData.
 * @type {number}
 */
o3d.RawData.prototype.length = 0;



/**
 * Discards all the resources associated with this data object.
 */
o3d.RawData.prototype.discard = function() {
  o3d.notImplemented();
};



/**
 * Flushes the memory resources associated with this data object,
 * but keeps a cache in case the data object is used later.
 */
o3d.RawData.prototype.flush = function() {
  o3d.notImplemented();
};



/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * The Texture class is a base class for image data used in texture mapping.
 * @constructor
 */
o3d.Texture = function() {
  o3d.ParamObject.call(this);

  /**
   * The memory format used for storing the bitmap associated with the texture
   * object.
   * @type {o3d.Texture.Format}
   */
  this.format = o3d.Texture.UNKNOWN_FORMAT;

  /**
   * The number of mipmap levels used by the texture.
   * @type {number}
   */
  this.levels = 1;

  /**
   * True if all the alpha values in the texture are 1.0
   * @type {boolean}
   */
  this.alphaIsOne = true;

  /**
   * The associated gl texture.
   * @type {WebGLTexture}
   * @private
   */
  this.texture_ = null;

  /**
   * The associated GL texture target: TEXTURE_2D or TEXTURE_CUBE_MAP.
   * This is the argument "target" to calls to bindTeture.
   * NOT THE SAME THING AS the argument "target" to texImage2D.
   * @type {number}
   * @private
   */
  this.texture_target_ = 0;

  /**
   * The width of the underlying webgl texture.
   * @type {number}
   * private
   */
  this.texture_width_ = 0;

  /**
   * The width of the underlying webgl texture.
   * @type {number}
   * private
   */
  this.texture_height_ = 0;

 /**
  * When texParameters get set, this keeps track of what they are so we don't
  * set them again next time if we don't have to.
  * @private
  */
  this.parameter_cache_ = {};
};
o3d.inherit('Texture', 'ParamObject');


/**
 * @type {number}
 */
o3d.Texture.Format = goog.typedef;

/**
 *  Format,
 *  UNKNOWN_FORMAT
 *  XRGB8
 *  ARGB8
 *  ABGR16F
 *  R32F
 *  ABGR32F
 *  DXT1
 *  DXT3
 *  DXT5
 *
 * The in-memory format of the texture bitmap.
 *
 * NOTE: The R32F format is different on GL vs D3D. If you use it in a shader
 * you must only use the red channel. The green, blue and alpha channels are
 * undefined.
 *
 * For example:
 *
 * ...
 *
 * sampler texSampler0;
 *
 * ...
 *
 * struct PixelShaderInput {
 *   float4 position : POSITION;
 *   float2 texcoord : TEXCOORD0;
 * };
 *
 * float4 pixelShaderFunction(PixelShaderInput input): COLOR {
 *   return tex2D(texSampler0, input.texcoord).rrrr;
 * }
 *
 * @param {number} levels The number of mip levels in this texture.
 */
o3d.Texture.UNKNOWN_FORMAT = 0;
o3d.Texture.XRGB8 = 1;
o3d.Texture.ARGB8 = 2;
o3d.Texture.ABGR16F = 3;
o3d.Texture.R32F = 4;
o3d.Texture.ABGR32F = 5;
o3d.Texture.DXT1 = 6;
o3d.Texture.DXT3 = 7;
o3d.Texture.DXT5 = 8;


/**
 * Generates Mips.
 * @param {number} source_level the mip to use as the source.
 * @param {number} num_levels the number of mips from the source to generate.
 */
o3d.Texture.prototype.generateMips =
    function(source_level, num_levels) {
  this.gl.bindTexture(this.texture_target_, this.texture_);
  this.gl.generateMipmap(this.texture_target_);
  this.levels = num_levels;
};


/**
 * Indicates whether the given number is a power of two.
 * @param {number} value The number.
 * @private
 */
o3d.Texture.isPowerOfTwo_ = function(value) {
  return (value & (value - 1)) == 0;
};


/**
 * Computes the smallest power of two that is greater than or equal to the
 * the given number.
 * @param {number} value The number.
 * @private
 */
o3d.Texture.nextHighestPowerOfTwo_ = function(value) {
  var r = 1;
  while (r < value) {
    r *= 2;
  }
  return r;
};


/**
 * Returns the GL texture format that is closest to this texture's O3D texture
 * format. Not all formats specified in o3d.Texture have a webgl equivalent,
 * thus we return the one with the correct number of channels, if such exists.
 *
 * @return {number} A webgl format.
 * @private
 */
o3d.Texture.prototype.getGLTextureFormat_ = function() {
  switch (this.format) {
    case o3d.Texture.XRGB8:
      return this.gl.RGB;

    case o3d.Texture.ARGB8:
    case o3d.Texture.ABGR16F:
    case o3d.Texture.ABGR32F:
      return this.gl.RGBA;

    case o3d.Texture.R32F:
    case o3d.Texture.DXT1:
    case o3d.Texture.DXT3:
    case o3d.Texture.DXT5:
    default:
      o3d.notImplemented();
      return 0;
  }
}


/**
 * Helper function to determine the proper argument for the texture target
 * This is either gl.TEXTURE_2D or gl.TEXTURE_CUBE_MAP_POSITIVE_X + face.
 *
 * @param {o3d.TextureCUBE.CubeFace} face The current face if applicable.
 * @return {number} The proper argument for the texture target.
 */
o3d.Texture.prototype.getTexImage2DTarget_ = function(opt_face) {
  if (this.texture_target_ == this.gl.TEXTURE_CUBE_MAP) {
    return this.gl.TEXTURE_CUBE_MAP_POSITIVE_X + opt_face;
  } else {
    return this.gl.TEXTURE_2D;
  }
};


/**
 * Computes the maximum number of levels of mips a given width and height could
 * use.
 * @param {number} width Width of texture.
 * @param {number} height Height of texture.
 * @return {number} The maximum number of levels for the given width and height.
 */
o3d.Texture.maxLevels_ = function(width, height) {
  if (width == 0 || height == 0) {
    return 0;
  }
  var max = Math.max(width, height);
  var levels = 0;
  while (max > 0) {
    ++levels;
    max = max >> 1;
  }
  return levels;
};


var g_counter = 0;


/**
 * Creates a webgl texture from the given image object rescaling to the
 * smallest power of 2 in each dimension still no smaller than the original
 * size.
 * @param {HTMLCanvas} canvas The canvas to load into the texture.
 * @param {boolean} resize_to_pot Whether or not to resize to a power of two
 *     size.
 * @param {boolean} generate_mips Whether or not to generate mips.
 * @param {o3d.TextureCUBE.CubeFace} opt_face The face number, if this is a
 *     cube map.
 * @private
 */
o3d.Texture.prototype.setFromCanvas_ =
    function(canvas, resize_to_pot, flip, generate_mips, opt_face) {
  var gl = this.gl;

  if (resize_to_pot && (!o3d.Texture.isPowerOfTwo_(canvas.width) ||
      !o3d.Texture.isPowerOfTwo_(canvas.height))) {
    // Get a scratch canvas.
    var scratch = o3d.Bitmap.getScratchCanvas_();
    // Set the size of the canvas to the power-of-two size.
    scratch.width = o3d.Texture.nextHighestPowerOfTwo_(canvas.width);
    scratch.height = o3d.Texture.nextHighestPowerOfTwo_(canvas.height);
    // Draw the given canvas into that scratch canvas.
    scratch.getContext("2d").drawImage(canvas,
        0, 0, canvas.width, canvas.height,
        0, 0, scratch.width, scratch.height);
    canvas = scratch;
  }

  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flip);

  gl.bindTexture(this.texture_target_, this.texture_);
  gl.texImage2D(this.getTexImage2DTarget_(opt_face), 0 /*level*/, gl.RGBA,
      gl.RGBA, gl.UNSIGNED_BYTE, canvas);

  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 0);


  this.texture_width_ = canvas.width;
  this.texture_height_ = canvas.height;

  if (generate_mips) {
    // The texture target is already bound so why bind it again by calling
    // this.generateMip.
    this.gl.generateMipmap(this.texture_target_);
    this.levels = o3d.Texture.maxLevels_(
        this.texture_width_, this.texture_height_);
  }

  g_counter++;
};


/**
 * Copy pixels from source bitmap to certain mip level.
 * Scales if the width and height of source and dest do not match.
 *
 * @param {HTMLCanvas} source_img The source canvas.
 * @param {number} source_x x-coordinate of the starting pixel in the
 *     source image.
 * @param {number} source_y y-coordinate of the starting pixel in the
 *     source image.
 * @param {number} source_width width of the source image to draw.
 * @param {number} source_height Height of the source image to draw.
 * @param {number} dest_mip on which mip level to draw to.
 * @param {number} dest_x x-coordinate of the starting pixel in the
 *     destination texture.
 * @param {number} dest_y y-coordinate of the starting pixel in the
 *     destination texture.
 * @param {number} dest_width width of the dest image.
 * @param {number} dest_height height of the dest image.
 * @param {number} opt_face The face number if this is a cube map.
 */
o3d.Texture.prototype.drawImageFromCanvas_ =
    function(source_canvas, source_x, source_y, source_width, source_height,
             dest_mip, dest_x, dest_y, dest_width, dest_height, opt_face) {
  var canvas = o3d.Bitmap.getScratchCanvas_();
  canvas.width = dest_width;
  canvas.height = dest_height;

  // Get a scratch canvas and set its size to that of the source material.
  // Set up transformation so that the draw into the canvas fill it with the
  // source bitmap.
  var context = canvas.getContext('2d');
  context.save();

  context.translate(-source_x, -source_y);
  context.scale(dest_width / source_width,
                dest_height / source_height);

  // Draw the source image into the canvas, filling it.
  context.drawImage(
      source_canvas, 0, 0, source_canvas.width, source_canvas.height);

  // Call texSubImage2D to upload the source image from the scratch canvas
  // to the texture.
  var gl = this.gl;
  gl.bindTexture(this.texture_target_, this.texture_);
  var format = this.getGLTextureFormat_();

  //*/
  // TODO(petersont): Replace this with a call to texSubImage2D once
  // browsers support it.
  gl.texImage2D(this.getTexImage2DTarget_(opt_face), dest_mip, gl.RGBA,
      gl.RGBA, gl.UNSIGNED_BYTE, canvas);
  /*/
  gl.texSubImage2D(target, 0, dest_x, dest_y, dest_width, dest_height,
      gl.RGBA, gl.UNSIGNED_BYTE, canvas);
  //*/
  this.texture_width_ = canvas.width;
  this.texture_height_ = canvas.height;

  context.restore();
};


/**
 * Sets the values of the data stored in the texture.
 *
 * @param {number} level the mip level to update.
 * @param {number} values Values to be stored in the buffer.
 * @param {o3d.TextureCUBE.CubeFace} opt_face The face to set if this is a cube
 *     texture.
 * @private
 */
o3d.Texture.prototype.setValues_ = function(level, values, opt_face) {
  var pixels = new Uint8Array(values.length);
  for (var i = 0; i < values.length; ++i) {
    pixels[i] = Math.min(255, Math.max(0, values[i] * 256.0));
  }

  var format = this.getGLTextureFormat_();
  this.gl.bindTexture(this.texture_target_, this.texture_);

  this.gl.texSubImage2D(this.getTexImage2DTarget_(opt_face),
      level, 0, 0, this.texture_width_, this.texture_height_,
      format, this.gl.UNSIGNED_BYTE, pixels);
};


/**
 * Initializes this Texture2D object of the specified size and format and
 * reserves the necessary resources for it.
 *
 * Note: If enable_render_surfaces is true, then the dimensions must be a
 * power of two.
 *
 * @param {number} texture_target The apropriate value for texture_target.
 * @param {number} width The width of the texture area in texels (max = 2048)
 * @param {number} height The height of the texture area in texels (max = 2048)
 * @param {o3d.Texture.Format} format The memory format of each texel
 * @param {number} levels The number of mipmap levels.  Use zero to create the
 *     compelete mipmap chain.
 * @param {boolean} enable_render_surfaces If true, the texture object will
 *     expose RenderSurface objects through GetRenderSurface(...).
 * @return {!o3d.Texture2D}  The Texture2D object.
 */
o3d.Texture.prototype.initWithTarget_ =
    function(texture_target, width, height, format, levels,
        enable_render_surfaces, debug) {
  this.width = width;
  this.height = height;
  this.format = format;
  this.levels = levels;
  this.texture_ = this.gl.createTexture();
  this.texture_target_ = texture_target;

  if (width != undefined && height != undefined) {
    this.gl.bindTexture(this.texture_target_, this.texture_);

    var format = this.getGLTextureFormat_();

    // TODO(petersont): remove this allocation once Firefox supports
    // passing null as argument to this form of ... some function.
    var pixels = new Uint8Array(width * height * 4);

    var canvas = o3d.Bitmap.getScratchCanvas_();
    canvas.width = width;
    canvas.height = height;

    var numFaces = 1;
    if (this.texture_target_ == this.gl.TEXTURE_CUBE_MAP) {
      numFaces = 6;
    }

    for (var face = 0; face < numFaces; ++face) {
      this.gl.texImage2D(this.getTexImage2DTarget_(face), 0, format, width,
          height, 0, format, this.gl.UNSIGNED_BYTE, pixels);
    }

    this.texture_width_ = width;
    this.texture_height_ = height;
  }
};


/**
 * A class for 2D textures that defines the interface for getting
 * the dimensions of the texture, its memory format and number of mipmap levels.
 *
 * @param {number} opt_width The width of this texture in pixels.
 * @param {number} opt_height The height of this texture in pixels.
 * @constructor
 */
o3d.Texture2D = function(opt_width, opt_height) {
  o3d.Texture.call(this);

  /**
   * The width of the texture, in texels.
   * @type {number}
   */
  this.width = opt_width || 0;

  /**
   * The height of the texture, in texels.
   * @type {number}
   */
  this.height = opt_height || 0;

  /**
   * The cache of rendersurface objects.
   * @private
   */
  this.renderSurfaces_ = [];
};
o3d.inherit('Texture2D', 'Texture');

o3d.ParamObject.setUpO3DParam_(o3d.Texture2D, 'width', 'ParamInteger');
o3d.ParamObject.setUpO3DParam_(o3d.Texture2D, 'height', 'ParamInteger');


/**
 * Initializes this Texture2D object of the specified size and format and
 * reserves the necessary resources for it.
 *
 * Note: If enable_render_surfaces is true, then the dimensions must be a
 * power of two.
 *
 * @param {number} width The width of the texture area in texels (max = 2048)
 * @param {number} height The height of the texture area in texels (max = 2048)
 * @param {o3d.Texture.Format} format The memory format of each texel
 * @param {number} levels The number of mipmap levels.  Use zero to create the
 *     compelete mipmap chain.
 * @param {boolean} enable_render_surfaces If true, the texture object will
 *     expose RenderSurface objects through GetRenderSurface(...).
 * @return {!o3d.Texture2D}  The Texture2D object.
 */
o3d.Texture2D.prototype.init_ =
    function(width, height, format, levels, enable_render_surfaces) {
  this.initWithTarget_(this.gl.TEXTURE_2D,
      width, height, format, levels, enable_render_surfaces);
};


/**
 * Returns a RenderSurface object associated with a mip_level of a texture.
 *
 * @param {number} mip_level The mip-level of the surface to be returned.
 * @return {o3d.RenderSurface}  The RenderSurface object.
 */
o3d.Texture2D.prototype.getRenderSurface =
    function(mip_level) {
  if (!this.renderSurfaces_[mip_level]) {
    var renderSurface = new o3d.RenderSurface();
    renderSurface.gl = this.gl;
    renderSurface.initWithTexture(this, mip_level);
    this.renderSurfaces_[mip_level] = renderSurface;
  }

  return this.renderSurfaces_[mip_level];
};


/**
 * Sets the values of the data stored in the texture.
 *
 * It is not recommend that you call this for large textures but it is useful
 * for making simple ramps or noise textures for shaders.
 *
 * NOTE: the number of values must equal the size of the texture * the number
 *  of elements. In other words, for a XRGB8 texture there must be
 *  width * height * 3 values. For an ARGB8, ABGR16F or ABGR32F texture there
 *  must be width * height * 4 values. For an R32F texture there must be
 *  width * height values.
 *
 * NOTE: the order of channels is R G B for XRGB8 textures and R G B A
 * for ARGB8, ABGR16F and ABGR32F textures so for example for XRGB8 textures
 *
 * [1, 0, 0] = a red pixel
 * [0, 0, 1] = a blue pixel
 *
 * For ARGB8, ABGR16F, ABGR32F textures
 *
 * [1, 0, 0, 0] = a red pixel with zero alpha
 * [1, 0, 0, 1] = a red pixel with one alpha
 * [0, 0, 1, 1] = a blue pixel with one alpha
 *
 * @param {number} level the mip level to update.
 * @param {values} values Values to be stored in the buffer.
 */
o3d.Texture2D.prototype.set =
    function(level, values) {
  this.setValues_(level, values);
};


/**
 * Sets a rectangular area of values in a texture.
 *
 * Does clipping. In other words if you pass in a 10x10 pixel array
 * and give it destination of (-5, -5) it will only use the bottom 5x5
 * pixels of the array you passed in to set the top 5x5 pixels of the
 * texture.
 *
 * See o3d.Texture2D.set for details on formats.
 *
 * @param {number} level the mip level to update.
 * @param {number} destination_x The x coordinate of the area in the texture
 *     to affect.
 * @param {number} destination_y The y coordinate of the area in the texture
 *     to affect.
 * @param {number} source_width The width of the area to effect. The height is
 *     determined by the size of the array passed in.
 * @param {number} values Values to be stored in the buffer.
 */
o3d.Texture2D.prototype.setRect =
    function(level, destination_x, destination_y, source_width, values) {
  var format = this.getGLTextureFormat_();
  var numChannels = (format == this.gl.RGB ? 3 : 4);
  var source_height = (values.length / numChannels) / source_width;

  // If completely clipped off, do nothing and return.
  if (destination_x > this.width || destination_y > this.height ||
      destination_x + source_width < 0 || destination_y + source_height < 0) {
    return;
  }

  // Size of the unclipped, remaining rectangle.
  var size_x = source_width;
  if (destination_x < 0) {
    size_x += destination_x;
  }
  if (destination_x + source_width > this.width) {
    size_x -= (destination_x + source_width) - this.width;
  }

  var size_y = source_height;
  if (destination_y < 0) {
    size_y += destination_y;
  }
  if (destination_y + source_height > this.height) {
    size_y -= (destination_y + source_height) - this.height;
  }

  // The upper left corner of the rectangle that is not clipped and will be
  // copied into the texture.
  var start_x = (destination_x < 0) ? Math.abs(destination_x) : 0;
  var start_y = (destination_y < 0) ? Math.abs(destination_y) : 0;

  var keptPixels = new Uint8Array(size_x * size_y * numChannels);
  var count = 0;
  for (var y = 0; y < size_y; ++y) {
    for (var x = 0; x < size_x; ++x) {
      var t = (((start_y + y) * source_width) + (start_x + x)) * numChannels;
      keptPixels[count++] = Math.min(255, Math.max(0, values[t] * 256.0));
      keptPixels[count++] = Math.min(255, Math.max(0, values[t + 1] * 256.0));
      keptPixels[count++] = Math.min(255, Math.max(0, values[t + 2] * 256.0));
      keptPixels[count++] = Math.min(255, Math.max(0, values[t + 3] * 256.0));
    }
  }

  // The (x, y) at which to begin drawing the rectangle on the original texture,
  // measured from the *BOTTOM* left hand corner.
  var where_x = Math.max(destination_x, 0);
  var where_y = this.height - (Math.max(destination_y, 0) + size_y);

  this.gl.bindTexture(this.texture_target_, this.texture_);
  this.gl.texSubImage2D(this.gl.TEXTURE_2D, level, where_x, where_y,
      size_x, size_y, format, this.gl.UNSIGNED_BYTE, keptPixels);
};


/**
 * Gets a rectangular area of values from a texture.
 *
 * See o3d.Texture2D.set for details on formats.
 * Can not be used for compressed textures.
 *
 * @param {number} level the mip level to get.
 * @param {number} x The x coordinate of the area in the texture to retrieve.
 * @param {number} y The y coordinate of the area in the texture to retrieve.
 * @param {number} width The width of the area to retrieve.
 * @param {number} height The height of the area to retrieve.
 * @return {number}  Array of pixel values.
 */
o3d.Texture2D.prototype.getRect =
    function(level, x, y, width, height) {
  o3d.notImplemented();
};


/**
 * Sets the content of the texture to the content of the bitmap. The texture
 * and the bitmap must be the same dimensions and the same format.
 *
 * @param {o3d.Bitmap} bitmap The bitmap to copy data from.
 */
o3d.Texture2D.prototype.setFromBitmap = function(bitmap) {
  // Whether to resize the texture to power-of-two size.
  var resize_to_pot = bitmap.defer_mipmaps_to_texture_;
  this.setFromCanvas_(bitmap.canvas_,
                      resize_to_pot,
                      bitmap.defer_flip_vertically_to_texture_,
                      bitmap.defer_mipmaps_to_texture_);
};


/**
 * Copy pixels from source bitmap to certain face and mip level.
 * Scales if the width and height of source and dest do not match.
 *
 * @param {o3d.Bitmap} source_img The source bitmap.
 * @param {number} source_mip which mip of the source to copy from.
 * @param {number} source_x x-coordinate of the starting pixel in the
 *     source image.
 * @param {number} source_y y-coordinate of the starting pixel in the
 *     source image.
 * @param {number} source_width width of the source image to draw.
 * @param {number} source_height Height of the source image to draw.
 * @param {number} dest_mip on which mip level to draw to.
 * @param {number} dest_x x-coordinate of the starting pixel in the
 *     destination texture.
 * @param {number} dest_y y-coordinate of the starting pixel in the
 *     destination texture.
 * @param {number} dest_width width of the destination image.
 * @param {number} dest_height height of the destination image.
 */
o3d.Texture2D.prototype.drawImage =
    function(source_img, source_mip, source_x, source_y, source_width,
             source_height, dest_mip, dest_x, dest_y, dest_width, dest_height) {
  this.drawImageFromCanvas_(source_img.canvas_,
      source_x, source_y, source_width, source_height, dest_mip,
      dest_x, dest_y, dest_width, dest_height);
};


/**
 * TextureCUBE is a class for textures used for cube mapping.  A cube texture
 * stores bitmaps for the 6 faces of a cube and is addressed via three texture
 * coordinates.
 *
 * @param {number} edgeLength The length of any edge of this texture
 * @constructor
 */
o3d.TextureCUBE = function() {
  o3d.Texture.call(this);

  /**
   * The length of each edge of the cube, in texels.
   * @type {number}
   */
  this.edgeLength = 0;

  /**
   * Keeps track of whether the faces of the cube map have been set to a
   * bitmap of some sort.  Used to prolong the generation of mipmaps until the
   * last cube face has been set to something.
   * @type {!Object}
   * @private
   */
   this.faces_set_ = {
     0: false, 1: false, 2: false, 3: false, 4: false, 5: false
   };
};
o3d.inherit('TextureCUBE', 'Texture');


/**
 * @type {number}
 */
o3d.TextureCUBE.CubeFace = goog.typedef;


/**
 *  CubeFace,
 *  FACE_POSITIVE_X
 *  FACE_NEGATIVE_X
 *  FACE_POSITIVE_Y
 *  FACE_NEGATIVE_Y
 *  FACE_POSITIVE_Z
 *  FACE_NEGATIVE_Z
 *
 * The names of each of the six faces of a cube map texture.
 */
o3d.TextureCUBE.FACE_POSITIVE_X = 0;
o3d.TextureCUBE.FACE_NEGATIVE_X = 1;
o3d.TextureCUBE.FACE_POSITIVE_Y = 2;
o3d.TextureCUBE.FACE_NEGATIVE_Y = 3;
o3d.TextureCUBE.FACE_POSITIVE_Z = 4;
o3d.TextureCUBE.FACE_NEGATIVE_Z = 5;

o3d.ParamObject.setUpO3DParam_(o3d.TextureCUBE, 'edgeLength', 'ParamInteger');


/**
 * Initializes this TextureCUBE object of the specified size and format and
 * reserves the necessary resources for it.
 * Note:  If enable_render_surfaces is true, then the dimensions must be a
 * power of two.
 *
 * @param {number} edgeLength The edge of the texture area in texels
 *     (max = 2048)
 * @param {o3d.Texture.Format} format The memory format of each texel.
 * @param {number} levels The number of mipmap levels.   Use zero to create
 *     the compelete mipmap chain.
 * @param {boolean} enableRenderSurfaces If true, the texture object
 *     will expose RenderSurface objects through GetRenderSurface(...).
 * @private
 */
o3d.TextureCUBE.prototype.init_ =
    function(edgeLength, format, levels, enable_render_surfaces, debug) {
  this.initWithTarget_(this.gl.TEXTURE_CUBE_MAP,
      edgeLength, edgeLength, format, levels, enable_render_surfaces, debug);
};


/**
 * Returns a RenderSurface object associated with a given cube face and
 * mip_level of a texture.
 *
 * @param {o3d.TextureCUBE.CubeFace} face The cube face from which to extract
 *     the surface.
 * @param {o3d.Pack} pack This parameter is no longer used. The surface exists
 *     as long as the texture it came from exists.
 * @param {number} mip_level The mip-level of the surface to be returned.
 * @return {o3d.RenderSurface}  The RenderSurface object.
 */
o3d.TextureCUBE.prototype.getRenderSurface =
    function(face, mip_level, opt_pack) {
  o3d.notImplemented();
};


/**
 * Sets the values of the data stored in the texture.
 *
 * It is not recommend that you call this for large textures but it is useful
 * for making simple ramps or noise textures for shaders.
 *
 * See o3d.Texture2D.set for details on formats.
 *
 * @param {o3d.TextureCUBE.CubeFace} face the face to update.
 * @param {number} level the mip level to update.
 * @param {number} values Values to be stored in the buffer.
 */
o3d.TextureCUBE.prototype.set =
    function(face, level, values) {
  this.setValues_(level, values, face);
  this.faces_set_[face] = true;
};


/**
 * Sets a rectangular area of values in a texture.
 *
 * Does clipping. In other words if you pass in a 10x10 pixel array
 * and give it destination of (-5, -5) it will only use the bottom 5x5
 * pixels of the array you passed in to set the top 5x5 pixels of the
 * texture.
 *
 * See o3d.Texture2D.set for details on formats.
 *
 * @param {o3d.TextureCUBE.CubeFace} face the face to update.
 * @param {number} level the mip level to update.
 * @param {number} destination_x The x coordinate of the area in the texture
 *     to affect.
 * @param {number} destination_y The y coordinate of the area in the texture
 *     to affect.
 * @param {number} source_width The width of the area to effect. The height is
 *     determined by the size of the array passed in.
 * @param {number} values Values to be stored in the buffer.
 */
o3d.TextureCUBE.prototype.setRect =
    function(face, level, destination_x, destination_y, source_width, values) {
  o3d.notImplemented();
};


/**
 * Gets a rectangular area of values from a texture.
 *
 * See o3d.Texture2D.set for details on formats.
 * Can not be used for compressed textures.
 *
 * @param {o3d.TextureCUBE.CubeFace} face the face to get.
 * @param {number} level the mip level to get.
 * @param {number} x The x coordinate of the area in the texture to retrieve.
 * @param {number} y The y coordinate of the area in the texture to retrieve.
 * @param {number} width The width of the area to retrieve.
 * @param {number} height The height of the area to retrieve.
 * @return {number}  Array of pixel values.
 */
o3d.TextureCUBE.prototype.getRect =
    function(face, level, x, y, width, height) {
  o3d.notImplemented();
};


/**
 * Sets the content of a face of the texture to the content of the bitmap. The
 * texture and the bitmap must be the same dimensions and the same format.
 *
 * @param {o3d.TextureCUBE.CubeFace} face The face to set.
 * @param {o3d.Bitmap} bitmap The bitmap to copy data from.
 */
o3d.TextureCUBE.prototype.setFromBitmap =
    function(face, bitmap) {
  var generate_mipmaps = bitmap.defer_mipmaps_to_texture_;
  for (var f in this.faces_set_) {
    generate_mipmaps = generate_mipmaps &&
        (this.faces_set_[f] || f == face);
  }
  var resize_to_pot = bitmap.defer_mipmaps_to_texture_;
  this.setFromCanvas_(bitmap.canvas_,
                      resize_to_pot,
                      false, // Never flip cube maps.
                      generate_mipmaps,
                      face);
  this.faces_set_[face] = true;
};


/**
 * Copy pixels from source bitmap to certain face and mip level.
 * Scales if the width and height of source and dest do not match.
 * TODO(petersont): Should take optional arguments.
 *
 * @param {o3d.Bitmap} source_img The source bitmap.
 * @param {number} source_mip which mip of the source to copy from.
 * @param {number} source_x x-coordinate of the starting pixel in the
 *     source image.
 * @param {number} source_y y-coordinate of the starting pixel in the
 *     source image.
 * @param {number} source_width width of the source image to draw.
 * @param {number} source_height Height of the source image to draw.
 * @param {o3d.TextureCUBE.CubeFace} face on which face to draw on.
 * @param {number} dest_mip on which mip level to draw on.
 * @param {number} dest_x x-coordinate of the starting pixel in the
 *     destination texture.
 * @param {number} dest_y y-coordinate of the starting pixel in the
 *     destination texture.
 * @param {number} dest_width width of the destination image.
 * @param {number} dest_height height of the destination image.
 */
o3d.TextureCUBE.prototype.drawImage =
    function(source_img, source_mip, source_x, source_y, source_width,
             source_height, face, dest_mip, dest_x, dest_y, dest_width,
             dest_height) {
  this.drawImageFromCanvas_(source_img.canvas_,
      source_x, source_y, source_width, source_height, dest_mip,
      dest_x, dest_y, dest_width, dest_height, face);
};


/**
 * Makes this texture currrent, and sets various texParameters provided they
 * have changed since the last time bindAndSetParameters_ was called for this
 * texture.
 * @param {number} addressModeU  The address mode in the U coordinate.
 * @param {number} addressModeV  The address mode in the V coordinate.
 * @param {number} minFilter The type of the min filter.
 * @param {number} magFilter The type of the mag filter.
 */
o3d.Texture.prototype.bindAndSetParameters_ =
    function(addressModeU, addressModeV, minFilter, magFilter) {
  var target = this.texture_target_;
  this.gl.bindTexture(target, this.texture_);

  if (!(o3d.Texture.isPowerOfTwo_(this.texture_width_) &&
        o3d.Texture.isPowerOfTwo_(this.texture_height_)) ||
        this.texture_target_ == this.gl.TEXTURE_CUBE_MAP) {
    addressModeU = addressModeV = this.gl.CLAMP_TO_EDGE;
  }

  if (this.parameter_cache_.addressModeU != addressModeU) {
    this.gl.texParameteri(target, this.gl.TEXTURE_WRAP_S, addressModeU);
    this.parameter_cache_.addressModeU = addressModeU;
  }

  if (this.parameter_cache_.addressModeV != addressModeV) {
    this.gl.texParameteri(target, this.gl.TEXTURE_WRAP_T, addressModeV);
    this.parameter_cache_.addressModeV = addressModeV;
  }

  if (this.parameter_cache_.minFilter != minFilter) {
    this.gl.texParameteri(target, this.gl.TEXTURE_MIN_FILTER, minFilter);
    this.parameter_cache_.minFilter = minFilter;
  }

  if (this.parameter_cache_.magFilter != magFilter) {
    this.gl.texParameteri(target, this.gl.TEXTURE_MAG_FILTER, magFilter);
    this.parameter_cache_.magFilter = magFilter;
  }
};


/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * Bitmap provides an interface for basic image operations on bitmap,
 * including scale and crop. A Bitmap can be created from RawData via
 * pack.createBitmapsFromRawData(), and also can be transferred to mip of a
 * Texure2D or a specific face of TextureCUBE via methods in Texture.
 * @constructor
 */
o3d.Bitmap = function() {
  o3d.ParamObject.call(this);
};
o3d.inherit('Bitmap', 'ParamObject');


/**
 * @type {number}
 */
o3d.Bitmap.Semantic = goog.typedef;

/**
 * After loading an array of Bitmaps with pack.createBitmapsFromRawData
 * you can inspect their semantic to see what they were intended for. This is
 * mostly to distinguish between 6 bitmaps that are faces of a cubemap and 6
 * bitmaps that are slices of a 3d texture.
 *
 *  FACE_POSITIVE_X, 1 face of a cubemap
 *  FACE_NEGATIVE_X, 1 face of a cubemap
 *  FACE_POSITIVE_Y, 1 face of a cubemap
 *  FACE_NEGATIVE_Y, 1 face of a cubemap
 *  FACE_POSITIVE_Z, 1 face of a cubemap
 *  FACE_NEGATIVE_Z, 1 face of a cubemap
 *  IMAGE, normal 2d image
 *  SLICE, a slice of a 3d texture.
 */
o3d.Bitmap.FACE_POSITIVE_X = 0;
o3d.Bitmap.FACE_NEGATIVE_X = 1;
o3d.Bitmap.FACE_POSITIVE_Y = 2;
o3d.Bitmap.FACE_NEGATIVE_Y = 3;
o3d.Bitmap.FACE_POSITIVE_Z = 4;
o3d.Bitmap.FACE_NEGATIVE_Z = 5;
o3d.Bitmap.IMAGE = 6;
o3d.Bitmap.SLICE = 7;


/**
 * The scratch canvas object.
 * @private
 */
o3d.Bitmap.scratch_canvas_ = null;


/**
 * Gets a canvas to use for scratch work.
 * @return {Canvas} The canvas.
 * @private
 */
o3d.Bitmap.getScratchCanvas_ = function() {
  if (!o3d.Bitmap.scratch_canvas_)
    o3d.Bitmap.scratch_canvas_ = document.createElement('CANVAS');
  return o3d.Bitmap.scratch_canvas_;
}


/**
 * In webgl the bitmap object is represented by an offscreen canvas.
 * @type {Canvas}
 * @private
 */
o3d.Bitmap.prototype.canvas_ = null;


/**
 * Flips a bitmap vertically in place.
 */
o3d.Bitmap.prototype.flipVertically = function() {
  var temp_canvas = document.createElement('CANVAS');
  temp_canvas.width = this.width;
  temp_canvas.height = this.height;
  var context = temp_canvas.getContext('2d');
  // Flip it.
  context.translate(0, this.height);
  context.scale(1, -1);
  context.drawImage(this.canvas_,
                    0, 0, this.width, this.height);
  this.canvas_ = temp_canvas;
};


/**
 * Flips a bitmap vertically in place lazily.
 * @private
 */
o3d.Bitmap.prototype.flipVerticallyLazily_ = function() {
  this.defer_flip_vertically_to_texture_ = true;
};


/**
 * Generates mip maps from the source level to lower levels.
 *
 * You can not currently generate mips for DXT textures although you can load
 * them from dds files.
 *
 * @param {number} source_level The level to use as the source of the mips.
 * @param {number} num_levels The number of levels to generate below the
 *     source level.
 */
o3d.Bitmap.prototype.generateMips =
    function(source_level, num_levels) {
  this.defer_mipmaps_to_texture_ = true;
};


/**
 * The width of the bitmap (read only).
 * @type {number}
 */
o3d.Bitmap.prototype.width = 0;



/**
 * The height of the bitmap (read only).
 * @type {number}
 */
o3d.Bitmap.prototype.height = 0;


/**
 * Instead of generating mipmaps in the bitmap object, just set this boolean
 * to true, then the texture will generate mipmaps when it loads the bitmap.
 * @type {boolean}
 * @private
 */
o3d.Bitmap.prototype.defer_mipmaps_to_texture_ = false;


/**
 * Instead of flipping vertically in the bitmap object, just set this boolean
 * to true, then the texture will generate mipmaps when it loads the bitmap.
 * @type {boolean}
 * @private
 */
o3d.Bitmap.prototype.defer_flip_vertically_to_texture_ = false;


/**
 * The format of the bitmap (read only).
 * @type {!o3d.Texture.Format}
 */
o3d.Bitmap.prototype.format = o3d.Texture.UNKNOWN_FORMAT;



/**
 * Number mip-maps (read only)
 * @type {number}
 */
o3d.Bitmap.prototype.numMipmaps = 1;



/**
 * The Semantic of the bitmap.
 * @type {!o3d.Bitmap.Semantic}
 */
o3d.Bitmap.prototype.semantic = o3d.Bitmap.UNKNOWN_SEMANTIC;



/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * A FileRequest is used to carry out an asynchronous request for a file
 * to be loaded.  Its use parallels that of XMLHttpRequest; you create one, call
 * open, set the onreadystatechange callback, and call send.
 * Note that unlike XMLHttpRequests, FileRequests cannot be reused.
 *
 * For RawData loads, on success the RawData will be stored in the data field
 * on the FileRequest itself. It is only valid until the FileRequest is freed by
 * calling pack.removeObject(request).
 *
 * var request = pack.createFileRequest("RAWDATA");
 * request.open("GET", url, true);
 * request.onreadystatechange = function() {
 *   if (request.done) {
 *     if (request.success) {
 *       var rawData = request.data;
 *
 *       ...
 *     } else {
 *       dump('Load of rawdata returned failure.');
 *     }
 *
 *     pack.removeObject(request);
 *   }
 * };
 * request.send();
 */
o3d.FileRequest = function() {
  this.method_ = "";
  this.listeners = [];
  this.async_ = true;
  this.request_ = new XMLHttpRequest();
  var fileRequest = this;
  this.request_.onreadystatechange = function() {
    fileRequest.readyState = this.readyState;
    fileRequest.done = fileRequest.done || this.done;
    if (this.readyState == 4) {
      if (this.responseText) {
        fileRequest.success = true;
      }
      fileRequest.done = true;
    }
    fileRequest.data = this.responseText;
    if (fileRequest.onreadystatechange)
      fileRequest.onreadystatechange.apply(fileRequest, arguments);
  }
  this.request_.onprogress = function(evt) {
    var lst = fileRequest.listeners;
	for (var i = 0, il = lst.length; i < il; i++) {
		if (lst[i].onProgressUpdate) {
			lst[i].onProgressUpdate(evt);
		}
		else {
			lst[i](evt);
		}
	}
  };

};
o3d.inherit('FileRequest', 'NamedObject');


/**
 * A callback to call whenever the ready state of the request changes.
 * @type {Object}
 */
o3d.FileRequest.prototype.onreadystatechange = null;

o3d.FileRequest.prototype.addProgressListener = function(listener) {
	this.listeners.push(listener);
};

/**
 * The URI this request is for.
 * @type {string}
 */
o3d.FileRequest.prototype.uri = '';



/**
 * On completion of successful RawData file loads, this holds the loaded
 * RawData.
 * @type {o3d.RawData}
 */
o3d.FileRequest.prototype.data = null;



/**
 * This holds the same values as in XMLHttpRequest:
 *  0 = uninitialized
 *  1 = opened
 *  2 = sent
 *  3 = receiving
 *  4 = loaded (the file has been downloaded, but may or may not have been
 * parsed yet)
 * @type {number}
 */
o3d.FileRequest.prototype.readyState = 0;



/**
 * This indicates whether any further processing will be done on this
 * FileRequest.
 * @type {boolean}
 */
o3d.FileRequest.prototype.done = false;



/**
 * This field is only valid if done is true.  It indicates whether or not the
 * request succeeded. If it failed error holds an error message.
 * @type {boolean}
 */
o3d.FileRequest.prototype.success = false;


/**
 * The image object if we are opening an image.
 * @type {Image}
 * @private
 */
o3d.FileRequest.prototype.image_ = null;


/**
 * An error message.
 * If done is true and success is false this will be an error message
 * describing what went wrong.
 * @type {string}
 */
o3d.FileRequest.prototype.error = '';


/**
 * Guesses from a url whether the url is an image file.
 * @param {string} url The URL.
 * @private
 */
o3d.FileRequest.prototype.isImageUrl_ = function(url) {
  var extension = url.substring(url.length - 4);
  return (extension == '.png' || extension == '.jpg');
};


/**
 * Called by the image class when the image file is loaded... if we're
 * loading an image.
 * @private
 */
o3d.FileRequest.prototype.imageLoaded_ = function() {
  if (this.image_.complete) {
    this.success = true;
    this.done = true;
    this.readyState = 4;
    this.data = new o3d.RawData();
    this.data.image_ = this.image_;
  }
  this.onreadystatechange.apply(this, arguments);
};


/**
 * Set up several of the request fields.
 * @param {string} method "GET" is the only supported method at this time
 * @param {string} uri the location of the file to fetch
 * @param {boolean} async true is the only legal value at this time.
 */
o3d.FileRequest.prototype.open =
    function(method, uri, async) {
  this.uri = uri;
  this.method_ = method;
  this.async_ = async;
};


/**
 * Send the request.
 * Unlike XMLHttpRequest the onreadystatechange callback will be called no
 * matter what, with success or failure.
 */
o3d.FileRequest.prototype.send = function() {
  if (this.isImageUrl_(this.uri)) {
    this.image_ = new Image();
    var that = this;
    this.image_.onload = function() {
      that.imageLoaded_.call(that);
    }
    this.image_.src = this.uri;
  } else {
    this.request_.open(this.method_, this.uri, this.async_);
  }
};



/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * A namespace to hold the list of all clients.
 * @namespace
 */
o3d.Renderer = {};


/**
 * @type {number}
 */
o3d.Renderer.InitStatus = goog.typedef;

/**
 * The initialization status of the renderer.
 *
 *  InitStatus,
 *  UNINITIALIZED,
 *  SUCCESS,  The renderer is initialized.
 *  GPU_NOT_UP_TO_SPEC,  The renderer determined the user's machine cannot
 *     run O3D.
 *  OUT_OF_RESOURCES,  The user's machine does not have enough graphic
 *     resources available to start another instance of the O3D renderer.
 *  INITIALIZATION_ERROR,  Some unknown error such as e.g. drivers not
 *     being installed correctly.
 */
o3d.Renderer.UNINITIALIZED = 0;
o3d.Renderer.SUCCESS = 1;
o3d.Renderer.GPU_NOT_UP_TO_SPEC = 2;
o3d.Renderer.OUT_OF_RESOURCES = 3;
o3d.Renderer.INITIALIZATION_ERROR = 4;


/**
 * @type {number}
 */
o3d.Renderer.DisplayMode = goog.typedef;

/**
 * This is used in SetFullscreenClickRegion to request the current display
 * mode, such that the change to full-screen mode won't change the screen
 * resolution or refresh rate.
 *
 *  DisplayModes,
 *  DISPLAY_MODE_DEFAULT
 */
o3d.Renderer.DISPLAY_MODE_DEFAULT = 0;


/**
 * The interval timer for the render callback.
 * @type {Object}
 */
o3d.Renderer.render_callback_interval_ = null;


/**
 * Global private list of all clients to be rendered every frame.
 * @type {!Array.<!o3d.Client>}
 */
o3d.Renderer.clients_ = [];


/**
 * Create the requestAnimationFrame function if needed. Each browser implements
 * it as d different name currently. Default to a timeout if not supported.
 * Credit to http://paulirish.com/2011/requestanimationframe-for-smart-animating/
 * and others...
 */
o3d.Renderer.installRequestAnimationFrame = function() {
  window.requestAnimationFrame = (function() {
    return window.mozRequestAnimationFrame    ||
           window.webkitRequestAnimationFrame ||
           window.oRequestAnimationFrame      ||
           window.msRequestAnimationFrame     ||
           function(callback, element) {
             setTimeout(callback, 1000 / 60);
           };
    })();
};


/**
 * Add the browser specific function to the window if it does no exist.
 */
if (!window.requestAnimationFrame) {
  o3d.Renderer.installRequestAnimationFrame();
}


/**
 * Renders all clients associated with this renderer.
 */
o3d.Renderer.renderClients = function(time) {
  for (var i = 0; i < o3d.Renderer.clients_.length; ++i) {
    var client = o3d.Renderer.clients_[i];
    client.counter_manager_.tick();
    if (client.renderMode == o3d.Client.RENDERMODE_CONTINUOUS) {
      client.render(time);
    }
  }
  window.requestAnimationFrame(o3d.Renderer.renderClients);
};


/**
 * Sets a timer to traverse the rendergraph every sixtieth of a second.
 */
o3d.Renderer.installRenderInterval = function() {
  o3d.Renderer.render_callback_interval_ = setInterval(
      "o3d.Renderer.renderClients()", 1000.0 / 60.0);
  window.requestAnimationFrame = function() {}; // Clear the browser version, using an interval instead
};


/**
 * The ClientInfo is used to get information about the client.
 * @constructor
 */
o3d.ClientInfo = function() {
  o3d.NamedObject.call(this);
};
o3d.inherit('ClientInfo', 'NamedObject');


/**
 * The number of objects the client is currently tracking.
 * You can use this to check that you are correctly freeing resources.
 * @type {number}
 */
o3d.ClientInfo.prototype.num_objects = 0;



/**
 * The amount of texture memory used.
 * @type {number}
 */
o3d.ClientInfo.prototype.texture_memory_used = 0;



/**
 * The amount of texture memory used.
 * @type {number}
 */
o3d.ClientInfo.prototype.buffer_memory_used = 0;



/**
 * Whether or not O3D is using the software renderer.
 *
 * For testing purposes you can force O3D to use the software renderer
 * by setting the environment variable O3D_FORCE_SOFTWARE_RENDERER to
 * anything.
 *
 *
 * set O3D_FORCE_SOFTWARE_RENDERER=foo
 *
 * or
 *
 * export O3D_FORCE_SOFTWARE_RENDERER=foo
 *
 *
 * You can set it at a system level if you want to set it for all
 * browser instances or set it from a command line and start your
 * browser from that same command line if you want to effect just
 * that instance of the browser.
 *
 * Note that many browers require special command line options to
 * run in a separate process, otherwise they default to finding
 * the browser process already running and using that. For example
 * firefox requires the option -no-remote.
 *
 * @type {boolean}
 */
o3d.ClientInfo.prototype.software_renderer = false;



/**
 * Whether or not the GPU supports non power of two textures.
 * NOTE: O3D always allows non power of two textures.
 *
 * The only reason to look at this flag is for things like video that are
 * updating the texture every frame. In that case, you might want to know
 * that you could run faster if you used a power of 2 texture instead of
 * a non power of 2 texture.
 *
 * @type {boolean}
 */
o3d.ClientInfo.prototype.non_power_of_two_textures = true;



/**
 * True if shaders need to be GLSL instead of Cg/HLSL.
 * In this implementation of o3d, that is true by default.
 **/
o3d.ClientInfo.prototype.glsl = true;



/**
 * The Client class is the main point of entry to O3D.  It defines methods
 * for creating and deleting packs. Each new object created by the Client is
 * assigned a unique ID.
 *
 * The Client has a root transform for the transform graph and a root render
 * node for the render graph.
 * @constructor
 */
o3d.Client = function() {
  o3d.NamedObject.call(this);

  var tempPack = this.createPack();
  this.root = tempPack.createObject('Transform');
  this.renderGraphRoot = tempPack.createObject('RenderNode');
  this.clientId = o3d.Client.nextId++;
  this.packs_ = [tempPack];
  this.clientInfo = tempPack.createObject('ClientInfo');
  this.counter_manager_ = new o3d.CounterManager;

  if (o3d.Renderer.clients_.length == 0)
    window.requestAnimationFrame(o3d.Renderer.renderClients);

  o3d.Renderer.clients_.push(this);

  /**
   * Stack of objects showing how the state has changed.
   * @type {!Array.<!Object>}
   * @private
   */
  this.stateMapStack_ = [];

  /**
   * An object containing an entry for each state variable that has changed.
   * @type {Object}
   * @private
   */
  this.stateVariableStacks_ = {};
};
o3d.inherit('Client', 'NamedObject');

/**
 * @type {function(!o3d.RenderEvent): void}
 */
o3d.Client.RenderCallback = goog.typedef;

/**
 * @type {function(!o3d.TickEvent): void}
 */
o3d.Client.TickCallback = goog.typedef;

/**
 * @type {function(string): void}
 */
o3d.Client.ErrorCallback = goog.typedef;

/**
 * The root of the render graph.
 * @type {o3d.RenderNode}
 */
o3d.Client.prototype.renderGraphRoot = null;


/**
 * Global counter to give the client a unique ID number.
 * @type {number}
 */
o3d.Client.nextId = 0;


/**
 * The time of the last render in seconds.
 * @type {number}
 */
o3d.Client.prototype.then_ = 0;


/**
 * The transform graph root.
 * @type {o3d.Transform}
 */
o3d.Client.prototype.root = null;


/**
 * A list of all packs for this client.
 * @type {!Array.<!o3d.Pack>}
 */
o3d.Client.prototype.packs_ = [];

/**
 * Keeps track of all counters associated with this client.
 * @type {o3d.CounterManager}
 */
o3d.Client.prototype.counter_manager_ = null;

/**
 * Whether or not the client sets the alpha channel of all pixels to 1 in the
 * final stage of rendering.
 *
 * By default, this is set to true to mimic the plugin's behavior. If
 * a transparent canvas background is desirable, this should be set to false.
 *
 * @type {boolean}
 */
o3d.Client.prototype.normalizeClearColorAlpha = true;

/**
 * Function that gets called when the client encounters an error.
 */
o3d.Client.prototype.error_callback = function(error_message) {
  alert(error_message);
};


/**
 * Function that gets called right before the client renders.
 */
o3d.Client.prototype.render_callback = function(render_event) {};


/**
 * Function that gets called every tick.
 */
o3d.Client.prototype.tick_callback = function(tick_event) {};


/**
 * Call this function from window.onunload to ensure the browser does not
 * continue to call callbacks (like the render callback) after the page is
 * unloaded.  It is possible that during unload the browser unloads all the
 * javascript code, but then, after that, still asks the plugin to render.  The
 * browser then calls javascript functions that no longer exist which causes an
 * error. To prevent that situation you need to clear all your callbacks on
 * unload. cleanup handles that for you so you don't have to dispose each and
 * every callback by hand.
 */
o3d.Client.prototype.cleanup = function () {
  this.clearRenderCallback();
  this.clearTickCallback();
  this.clearErrorCallback();
};


/**
 * Creates a pack object.
 *   A pack object.
 * @return {!o3d.Pack} A new pack object.
 */
o3d.Client.prototype.createPack =
    function() {
  var pack = new o3d.Pack;
  pack.client = this;
  pack.gl = this.gl;
  this.packs_.push(pack);
  return pack;
};


/**
 * Creates a pack object.
 *   A pack object.
 * @param {!o3d.Pack} pack The pack to remove.
 */
o3d.Client.prototype.destroyPack =
    function(pack) {
  o3d.removeFromArray(this.packs_, pack);
};



/**
 * Searches the Client for an object matching the given id.
 *
 * @param {number} id The id of the object to look for.
 * @return {o3d.ObjectBase}  The object or null if a object
 *     with the given id is not found.
 */
o3d.Client.prototype.getObjectById =
    function(id) {
  o3d.notImplemented();
};


/**
 * Searches the Client for objects of a particular name and type.
 * @param {string} name name of object to look for.
 * @param {string} class_name name of class to look for.
 * @return {!Array.<!o3d.ObjectBase>}  Array of objects found.
 */
o3d.Client.prototype.getObjects =
    function(name, class_name) {
  var objects = [];

  for (var i = 0; i < this.packs_.length; ++i) {
    var pack = this.packs_[i];
    objects = objects.concat(pack.getObjects(name, class_name));
  }

  return objects;
};


/**
 * Searches the Client for objects of a particular type.
 * @param {string} class_name name of class to look for.
 * @return {!Array.<!Object>}  Array of objects found.
 */
o3d.Client.prototype.getObjectsByClassName =
    function(class_name) {
  var objects = [];

  for (var i = 0; i < this.packs_.length; ++i) {
    var pack = this.packs_[i];
    objects = objects.concat(pack.getObjectsByClassName(class_name));
  }

  return objects;
};


/**
 * @type {number}
 */
o3d.Client.RenderMode = goog.typedef;

/**
 *  RENDERMODE_CONTINUOUS,  Draw as often as possible up to refresh rate.
 *  RENDERMODE_ON_DEMAND,   Draw once then only when the OS requests it
 *      (like uncovering part of a window.)
 */
o3d.Client.RENDERMODE_CONTINUOUS = 0;
o3d.Client.RENDERMODE_ON_DEMAND = 1;


/**
 * The current render mode. The default mode is RENDERMODE_CONTINUOUS.\n
 * Valid values are:
 *  RENDERMODE_CONTINUOUS,  Draw as often as possible up to refresh rate.
 *  RENDERMODE_ON_DEMAND,   Draw when the OS requests it (like uncovering
 *                             part of a window.)
 * @type {o3d.Client.RenderMode}
 */
o3d.Client.prototype.renderMode = o3d.Client.RENDERMODE_CONTINUOUS;


/**
 * Forces a render of the current scene if the current render mode is
 * RENDERMODE_ON_DEMAND.
 */
o3d.Client.prototype.render = function(now) {
  if (!this.gl) {
    return;
  }
  // Synthesize a render event.
  var render_event = new o3d.RenderEvent;
  this.counter_manager_.advanceRenderFrameCounters();

  this.clearStateStack_();

  if (!now) {
    now = new Date().getTime();
  }
  now *= 0.001;
  if(this.then_ == 0.0)
    render_event.elapsedTime = 0.0;
  else
    render_event.elapsedTime = now - this.then_;

  if (this.render_callback) {
    for (var stat in this.render_stats_) {
      render_event[stat] = this.render_stats_[stat];
    }
    this.render_callback(render_event);
  }
  this.then_ = now;

  this.gl.colorMask(true, true, true, true);

  this.renderTree(this.renderGraphRoot);

  // When o3d finally draws to the webpage, the alpha channel should be all 1's
  // So we clear with a color mask to set all alpha bytes to 1 before drawing.
  // Before we draw again, the color mask gets set back to all true (above).
  if (this.normalizeClearColorAlpha) {
    this.gl.colorMask(false, false, false, true);
    this.gl.clearColor(0.0, 0.0, 0.0, 1.0);
    this.gl.clear(this.gl.COLOR_BUFFER_BIT);
  }
};


/**
 * An object for various statistics that are gather during the render tree
 * tranversal.
 *
 * @type {Object}
 */
o3d.Client.prototype.render_stats = {}


/**
 * Renders a render graph.
 *
 * Normally the client calls this function automatically for you effectively
 * doing a client.renderTree(client.renderGraphRoot) but there are cases
 * where it is beneficial to be able to call this yourself and pass it
 * different roots when you need to manipulate something between calls.
 *
 * This function can only be called from inside a render callback. If you call
 * it the client will not do its default call as mentioned above.
 *
 * @param {!o3d.RenderNode} render_node root RenderNode to start rendering from.
 */
o3d.Client.prototype.renderTree =
    function(render_node) {
  this.render_stats_ = {
    drawElementsCulled: 0,
    drawElementsProcessed: 0,
    drawElementsRendered: 0,
    primitivesRendered: 0,
    transformsCulled: 0,
    transformsProcessed: 0
  };

  render_node.render();
};


/**
 * Returns an array of DisplayModes which are available for use in full-screen
 * mode.
 *  An array of DisplayModes.
 * @type {!Array.<!o3d.Client.DispalyMode>}
 */
o3d.Client.prototype.getDisplayModes = [];


/**
 * Makes a region of the plugin area that will invoke full-screen mode if
 * clicked.  The developer is responsible for communicating this to the user,
 * as this region has no visible marker.  The developer is also responsible for
 * updating this region if the plugin gets resized, as we don't know whether or
 * how to scale it.  There can be only one full-screen click region at a time;
 * calling this again will override any previous call.
 *
 * @param {number} x x position in pixels.
 * @param {number} y y position in pixels.
 * @param {number} width width in pixels.
 * @param {number} height height in pixels.
 * @param {number} mode_id Id of mode to use.
 */
o3d.Client.prototype.setFullscreenClickRegion =
    function(x, y, width, height, mode_id) {
  o3d.notImplemented();
};


/**
 * Deactivates the plugin click region that was previously created with
 * SetFullscreenClickRegion().
 */
o3d.Client.prototype.clearFullscreenClickRegion = function() {
  o3d.notImplemented();
};



/**
 * Cancels full-screen display, reverting to displaying content only in the
 * plugin region.  If the plugin is already not in full-screen mode, this has
 * no effect.  This does not deactivate the plugin click region--if the user
 * clicks there again, we'll go back to full-screen display.
 */
o3d.Client.prototype.cancelFullscreenDisplay = function() {
  render_node.render();
};



/**
 * Gets info about the client.
 * @type {!o3d.ClientInfo}
 */
o3d.Client.prototype.client_info = null;



/**
 * Whether content is displayed in full-screen mode or in a plugin window.  The
 * default is false [not full-screen].
 * @type {boolean}
 */
o3d.Client.prototype.fullscreen = false;


/**
 * Returns the width of the current drawing area [plugin or full-screen] in
 * pixels.
 */
o3d.Client.prototype.__defineGetter__('width',
    function() {
      return this.gl.canvas.width;
    }
);

o3d.Client.prototype.__defineSetter__('width',
    function(x) {
      this.gl.canvas.width = x;
    }
);


/**
 * Returns the height of the current drawing area [plugin or full-screen] in
 * pixels.
 */
o3d.Client.prototype.__defineGetter__('height',
    function() {
      return this.gl.canvas.height;
    }
);

o3d.Client.prototype.__defineSetter__('height',
    function(x) {
      this.gl.canvas.height = x;
    }
);


/**
 * Initializes this client using the canvas.
 * @param {Canvas}
 * @return {boolean} Success.
 */
o3d.Client.prototype.initWithCanvas = function(canvas) {
  var gl;

  var standard_attributes = {
    alpha : true,
    depth : true,
    stencil : true,
    antialias : true,
    premultipliedAlpha : true
  };

  if (!canvas || !canvas.getContext) {
    return false;
  }

  var names = ["webgl", "experimental-webgl", "moz-webgl"];
  for (var ii = 0; ii < names.length; ++ii) {
    try {
      gl = canvas.getContext(names[ii], standard_attributes)
    } catch(e) { }
    if (gl) {
      break;
    }
  }

  if (!gl) {
    return false;
  }

  this.gl = gl;
  this.root.gl = gl;
  this.renderGraphRoot.gl = gl;

  gl.client = this;
  gl.displayInfo = {width: canvas.width,
                    height: canvas.height};
  o3d.State.createDefaultState_(gl).push_();

  this.initErrorTextures_();

  return true;
}


/**
 * Creates the yellow-and-red 8x8 error textures.
 * @private
 */
o3d.Client.prototype.initErrorTextures_ = function() {
  // First create the yellow-and-red pattern.
  var r = [1, 0, 0, 1];
  var Y = [1, 1, 0, 1];
  var error = [r, r, r, r, r, r, r, r,
               r, r, Y, Y, Y, Y, r, r,
               r, Y, r, r, r, Y, Y, r,
               r, Y, r, r, Y, r, Y, r,
               r, Y, r, Y, r, r, Y, r,
               r, Y, Y, r, r, r, Y, r,
               r, r, Y, Y, Y, Y, r, r,
               r, r, r, r, r, r, r, r];
  var pixels = [];
  for (var i = 0; i < error.length; i++) {
    for (var j = 0; j < 4; j++) {
      pixels[i * 4 + j] = error[i][j];
    }
  }

  // Create the default error cube map using the same data.
  var defaultTextureCube = new o3d.TextureCUBE();

  defaultTextureCube.gl = this.gl;
  defaultTextureCube.init_(8, o3d.Texture.ARGB8, 1, false, true);

  for (var i = 0; i < 6; ++i) {
    defaultTextureCube.set(i, 0, pixels);
  }
  defaultTextureCube.name = 'DefaultTextureCube';
  this.error_texture_cube_ = defaultTextureCube;
  this.fallback_error_texture_cube_ = defaultTextureCube;

  // Create the default error texture.
  var defaultTexture = new o3d.Texture2D();
  defaultTexture.gl = this.gl;
  defaultTexture.init_(8, 8, o3d.Texture.ARGB8, 1, false);

  defaultTexture.set(0, pixels);
  defaultTexture.name = 'DefaultTexture';
  this.error_texture_ = defaultTexture;
  this.fallback_error_texture_ = defaultTexture;
};


/**
 * Sets the per frame render callback.
 *
 * Note: The callback will not be called recursively. When your callback is
 * called if you somehow manage to cause the client to render more frames
 * before you've returned from the callback you will not be called for those
 * frames.
 *
 * g_client.setRenderCallback(onrender);
 *
 * function onrender(render_event) {
 *   var elapsedTime = render_event.elapsedTime;
 * }
 *
 * @param {!o3d.RenderCallback} render_callback The callback to call
 *     each frame.
 */
o3d.Client.prototype.setRenderCallback =
    function(render_callback) {
  this.render_callback = render_callback;
};


/**
 * Clears the per frame render callback.
 */
o3d.Client.prototype.clearRenderCallback = function() {
  this.render_callback = null;
};


/**
 * Sets a render callback to be called at the end of the
 * rendering cycle of each frame.
 *
 * Note: The callback will not be called recursively. When your callback is
 * called if you somehow manage to cause the client to render more frames
 * before you've returned from the callback you will not be called for those
 * frames.
 *
 *
 * g_client.setPostRenderCallback(onpostrender);
 *
 * function onpostrender(render_event) {
 *   var elapsedTime = render_event.elapsedTime;
 * }
 *
 * @param {!o3d.RenderCallback} post_render_callback The callback to call
 *     each frame.
 */
o3d.Client.prototype.setPostRenderCallback =
    function(post_render_callback) {
  this.postRenderCallback = post_render_callback;
};


/**
 * Clears the post render callback.
 */
o3d.Client.prototype.clearPostRenderCallback = function() {
  this.postRenderCallback = null;
};



/**
 * Sets the lost resources callback.
 *
 * The contents of certain resources, RenderSurfaces, can get discarded by the
 * system under certain circumstances. If you application needs that contents
 * to be in a certain state then you can set a callback giving your program the
 * opportunity to restore that state if and when it is lost.
 *
 * @param {!o3d.LostResourcesCallback} lost_resources_callback The callback when
 *     resources are lost.
 */
o3d.Client.prototype.setLostResourcesCallback =
    function(lost_resources_callback) {
  this.lostResourcesCallback = lost_resources_callback;
};


/**
 * Clears the lost resources callback.
 */
o3d.Client.prototype.clearLostResourcesCallback =
    function() {
  this.lostResourcesCallback = null;
};


/**
 * Returns the event object for processing.
 * @param {Event} event A mouse-related DOM event.
 * @private
 */
o3d.Client.getEvent_ = function(event) {
  return event ? event : window.event;
};


/**
 * Returns an object with event, element, name and wheel in a semi cross
 * browser way. Note: this can only be called from since an event because
 * depending on the browser it expects that window.event is valid.
 * @param {!Event} event A mouse-related DOM event.
 * @return {!EventInfo} Info about the event.
 * @private
 */
o3d.Client.getEventInfo_ = function(event) {
  var elem = event.target ? event.target : event.srcElement;
  var name = elem.id ? elem.id : ('->' + elem.toString());
  var wheel = event.detail ? event.detail : -event.wheelDelta;
  return { event: event,
           element: elem,
           name: name,
           wheel: wheel };
};


/**
 * Returns the absolute position of an element.
 * @param {!HTMLElement} element The element to get a position for.
 * @return {!Object} An object containing x and y as the absolute position
 *     of the given element.
 * @private
 */
o3d.Client.getAbsolutePosition_ = function(element) {
  var r = {x: 0, y: 0};
  for (var e = element; e; e = e.offsetParent) {
    r.x += e.offsetLeft;
    r.y += e.offsetTop;
  }
  return r;
};

/**
 * Compute the x and y of an event with respect to the element which received
 * the event.
 *
 * @param {!Object} eventInfo As returned from
 *     o3d.Client.getEventInfo.
 * @return {!Object} An object containing keys 'x' and 'y'.
 * @private
 */
o3d.Client.getLocalXY_ = function(eventInfo) {
  var event = eventInfo.event;
  var p = o3d.Client.getAbsolutePosition_(eventInfo.element);
  return {x: event.pageX - p.x, y: event.pageY - p.y};
};


/**
 * Wraps a user's event callback with one that properly computes
 * relative coordinates for the event.
 * @param {!o3d.EventCallback} handler Function to call on event.
 * @param {boolean} doCancelEvent If event should be canceled after callback.
 * @return {!o3d.EventCallback} Wrapped handler function.
 * @private
 */
o3d.Client.wrapEventCallback_ = function(handler, doCancelEvent) {
  return function(event) {
    event = o3d.Client.getEvent_(event);
    var originalEvent = event;
    var info = o3d.Client.getEventInfo_(event);
    var relativeCoords = o3d.Client.getLocalXY_(info);
    // In a proper event, there are read only properties, so we clone it.
    event = o3d.clone(event);
    event.x = relativeCoords.x;
    event.y = relativeCoords.y;
    // Invert value to meet contract for deltaY. @see event.js.
    event.deltaY = -info.wheel;
    handler(event);
    if (doCancelEvent) {
      // Need to cancel the original, un-cloned event.
      o3djs.event.cancel(originalEvent);
    }
  };
};


/**
 * Sets a callback for a given event type.
 * types.
 * There can be only one callback for a given event type at a time; setting a
 * new one deletes the old one.
 *
 * @param {string} type Type of event to set callback for.
 * @param {!o3d.EventCallback} handler Function to call on event.
 */
o3d.Client.prototype.setEventCallback =
    function(type, handler) {
  var listener = this.gl.canvas;
  // TODO(petersont): Figure out a way for a canvas to listen to a key event
  // directly.

  var isWheelEvent = type == 'wheel';
  var forKeyEvent = type.substr(0, 3) == 'key';
  if (forKeyEvent) {
    listener = document;
  } else {
    handler = o3d.Client.wrapEventCallback_(handler, isWheelEvent);
  }
  if (isWheelEvent) {
    listener.addEventListener('DOMMouseScroll', handler, true);
    listener.addEventListener('mousewheel', handler, true);
  } else {
    listener.addEventListener(type, handler, true);
  }
};


/**
 * Removes the previously-registered callback for an event of the given type.
 * @param {string} type Type of event to clear callback for.
 */
o3d.Client.prototype.clearEventCallback =
    function(type) {
  //TODO(petersont): Same as TODO in setEventCallback above.
  var listener = this.gl.canvas;

  var isWheelEvent = type == 'wheel';
  var forKeyEvent = type.substr(0, 3) == 'key';
  if (forKeyEvent) {
    listener = document;
  }

  if (isWheelEvent) {
    listener.removeEventListener('DOMMouseScroll');
    listener.removeEventListener('mousewheel');
  } else {
    listener.removeEventListener(type);
  }
};


/**
 * Sets the texture to use when a Texture or Sampler is missing while
 * rendering. The default is a red texture with a yellow no symbol.
 * <span style="color:yellow; background-color: red;">&Oslash;.
 * If you set it to null you'll get an error if you try to render something
 * that is missing a needed Texture, Sampler or ParamSampler.
 *
 * For example if you don't care about missing textures, setting it to a black
 * texture would be one option. Another example is if you want to write all
 * your shaders to expect a texture then set this to a white texture. If you
 * want to make sure you are not missing any textures set it null and see if
 * you get any errors using Client.setErrorCallback or Client.lastError.
 *
 * var t = g_pack.createTexture2D('', 1, 1, g_o3d.Texture.XRGB8, 1);
 * t.set(0, [0, 0, 0]);
 * g_client.setErrorTexture(t);
 *
 * @param {o3d.Texture} texture texture to use for missing textures or null.
 */
o3d.Client.prototype.setErrorTexture =
    function(texture) {
  this.error_texture_ = texture;
};


/**
 * Sets the cubemap texture to use when a Texture or Sampler is missing while
 * rendering. The default is a red texture with a yellow no symbol.
 * <span style="color:yellow; background-color: red;">&Oslash;.
 * If you set it to null you'll get an error if you try to render something
 * that is missing a needed Texture, Sampler or ParamSampler.
 *
 * @param {o3d.Texture} texture Texture to use for missing textures or null.
 */
o3d.Client.prototype.setErrorTextureCube =
    function(texture) {
  this.error_texture_cube_map_ = texture;
};


/**
 * Sets a callback for when the client ticks. The client processes some things
 * like animation timers at up to 100hz.  This callback will get called before
 * each of those process ticks.
 *
 * NOTE: The client takes ownership of the TickCallback you
 * pass in. It will be deleted if you call SetTickCallback a
 * second time or if you call clearTickCallback.
 *
 * Note: The callback will not be called recursively.
 *
 * @param {o3d.TickCallback} tick_callback TickCallback to call when the
 *     Client ticks.
 */
o3d.Client.prototype.setTickCallback =
    function(tick_callback) {
  this.tickCallback = tick_callback;
};


/**
 * Clears the tick callback
 *
 * NOTE: The client takes ownership of the TickCallback you
 * pass in. It will be deleted if you call SetTickCallback a second
 * time or if you call clearTickCallback
 */
o3d.Client.prototype.clearTickCallback = function() {
  this.tickCallback = null;
};


/**
 * Sets a callback for when the client gets an error. For example when a shader
 * is compiled and there is an error or if you attempt to bind a param to a
 * param of an incompatible type.
 *
 * NOTE: The client takes ownership of the ErrorCallback you
 * pass in. It will be deleted if you call SetErrorCallback a
 * second time or if you call ClearErrorCallback.
 *
 * NOTE: The callback will not be called recursively. If you are in a
 * callback, and do something that causes another error before you have
 * returned from the callback, your callback will not be called a second time.
 *
 * NOTE: If you put up an alert in response to an error it is best if you
 * clear the error callback before you put up the alert. Otherwise you'll get
 * an alert everytime the client tries to render which is every time you close
 * the current alert which means you'll be in an infinite loop of alerts.
 *
 * @param {o3d.ErrorCallback} error_callback ErrorCallback to call when the
 *     Client gets an error.
 */
o3d.Client.prototype.setErrorCallback =
    function(error_callback) {
  // Other code expects to not see a null error callback.
  if (error_callback) {
    this.error_callback = this.wrapErrorCallback_(error_callback);
  } else {
    this.error_callback = function(string) {
      this.last_error_ = string;
    };
  }
};


/**
 * Wraps a callback function, saving the error string so that the
 * lastError variable can access it.
 *
 * @param {function} error_callback User-defined error callback.
 * @return {function} Wrapped error callback.
 * @private
 */
o3d.Client.prototype.wrapErrorCallback_ = function(error_callback) {
  return function(string) {
    this.last_error_ = string;
    error_callback(string);
  }
}


/**
 * Clears the Error callback
 *
 * NOTE: The client takes ownership of the ErrorCallback you
 * pass in. It will be deleted if you call SetErrorCallback a second
 * time or if you call ClearErrorCallback.
 */
o3d.Client.prototype.clearErrorCallback = function() {
  this.setErrorCallback(null);
};



/**
 * Makes all parameters get re-evaluated.
 */
o3d.Client.prototype.invalidateAllParameters = function() {
  o3d.notImplemented();
};


/**
 * Gets a copy of the current backbuffer of O3D as a data: url.
 * @param {string} mime_type The type of data url you want.
 *    Currently O3D only supports image/png. See HTML5 canvas tag
 *    for info about toDataURL.
 * @return {string}  A Data URL for the backbuffer.
 */
o3d.Client.prototype.toDataURL =
    function(opt_mime_type) {
  o3d.notImplemented();
};


/**
 * Saves data needed to return state to current, then sets the state according
 * to the given object.
 * @private
 */
o3d.Client.prototype.clearStateStack_ = function() {
  this.stateMapStack_ = [];
  for (var name in this.stateVariableStacks_) {
    var l = this.stateVariableStacks_[name];
    // Recall there is a default value at the bottom of each of these stacks.
    if (l.length != 1) {
      this.stateVariableStacks_[name] = l.slice(0, 1);
    }
  }
};


/**
 * Saves data needed to return state to current, then sets the state according
 * to the given object.
 * @param {Object} variable_map A map linking names to values.
 * @private
 */
o3d.Client.prototype.pushState_ = function(variable_map) {
  // Save the variable map itself in a stack.
  this.stateMapStack_.push(variable_map);

  // Save each of the state's variable's value in its own stack.
  for (var name in variable_map) {
    var value = variable_map[name];
    if (this.stateVariableStacks_[name] == undefined) {
      this.stateVariableStacks_[name] = [];
    }
    this.stateVariableStacks_[name].push(value);
  }

  // The value on the top of the stack in stateVariableStacks_
  // is current at this point.
  o3d.State.setVariables_(this.gl, variable_map);
};


/**
 * Returns the state to the way it was before the current state
 * @private
 */
o3d.Client.prototype.popState_ = function() {
  var variable_map = this.stateMapStack_.pop();

  for (var name in variable_map) {
    var stack = this.stateVariableStacks_[name];
    stack.pop();
    variable_map[name] = stack.length ? stack[stack.length - 1] :
        o3d.State.stateVariableInfos_[name]['defaultValue'];
  }

  o3d.State.setVariables_(this.gl, variable_map);
};


/**
 * Gets the current value of the state variable with the give name.
 * @param {string} name The name of the state variable in question.
 * @private
 */
o3d.Client.prototype.getState_ = function(name) {
  var stack = this.stateVariableStacks_[name];
  return stack.length ? stack[stack.length - 1] :
        o3d.State.stateVariableInfos_[name]['defaultValue'];
};


/**
 * Returns the status of initializing the renderer so we can display the
 * appropriate message. We require a certain minimum set of graphics
 * capabilities. If the user's computer does not have his minimum
 * set this will be GPU_NOT_UP_TO_SPEC. If the user is out of graphics
 * resources this will be OUT_OF_RESOURCES. If some other error happened this
 * will be INITIALIZATION_ERROR. Otherwise it will be SUCCESS.
 */
o3d.Client.prototype.renderer_init_status = 0;


/**
 * Gets / Sets the cursor's shape.
 *
 * @type {o3d.Cursor}
 */
o3d.Client.prototype.cursor = null;


/**
 * The current error texture.
 *
 * @type {o3d.Texture2D}
 * @private
 */
o3d.Client.prototype.error_texture_ = null;


/**
 * The current error cube texture.
 *
 * @type {o3d.TextureCUBE}
 * @private
 */
o3d.Client.prototype.error_texture_cube_ = null;


/**
 * The fallback error texture. Should only be initialized once per client and
 * is read-only.
 *
 * @type {!o3d.Texture2D}
 * @private
 */
o3d.Client.prototype.fallback_error_texture_ = null;


/**
 * The fallback error texture. Should only be initialized once per client and
 * is read-only.
 *
 * @type {!o3d.TextureCUBE}
 * @private
 */
o3d.Client.prototype.fallback_error_texture_cube_ = null;


/**
 * The last error reported by the plugin.
 *
 * @type {string}
 * @private
 */
o3d.Client.prototype.last_error_ = '';
o3d.Client.prototype.__defineGetter__('lastError',
    function() {
      return this.last_error_;
    }
);

/**
 * Returns true if missing textures, samplers or ParamSamplers should be
 * reported by calling the error callback. We assume that if the user
 * explicitly sets the error texture to null, then they want such errors to
 * trigger the error callback.
 *
 * @return {boolean}
 * @private
 */
o3d.Client.prototype.reportErrors_ = function() {
  return (this.error_texture_ == null);
}


/**
 * All the objects managed by this client.
 *
 * Each access to this field gets the entire list so it is best to get it
 * just once. For example:
 *
 * var objects = client.objects;
 * for (var i = 0; i < objects.length; i++) {
 *   var object = objects[i];
 * }
 *
 *
 * Note that modifications to this array [e.g. push()] will not affect
 * the underlying Client, while modifications to the array's members
 * will affect them.
 *
 * @type {!Array.<!o3d.ObjectBase>}
 */
o3d.Client.prototype.objects = [];



/**
 * Clears the error returned in lastError.
 */
o3d.Client.prototype.clearLastError = function () {
  this.last_error_ = '';
};



/**
 * Resets the profiling information.
 */
o3d.Client.prototype.profileReset = function() {
  o3d.notImplemented();
};



/**
 * Returns the profiling information as a string.
 *  The profiling info.
 * @return {string}
 */
o3d.Client.prototype.profileToString = function() {
  o3d.notImplemented();
};



/**
 * A unique id for this client.
 * @type {number}
 */
o3d.Client.prototype.clientId = 0;



/**
 * Gets info about the client.
 */
o3d.Client.prototype.clientInfo = null;


/**
 * The canvas associated with this client.
 * @type {Element}
 */
o3d.Client.prototype.canvas = null;
/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * RenderNode is the base of all RenderNodes in the render graph.
 * RenderNodes are rendered in order of priority.
 *
 * @param {number} opt_priority The priority of this render node. Lower
 *     priorities are rendered first.
 * @param {boolean} opt_active If true this node is processed. If false
 *     it is not.
 * @constructor
 */
o3d.RenderNode = function(opt_priority, opt_active) {
  o3d.ParamObject.call(this);

  /**
   * Sets the priority of this render node. lower priorities are
   * rendered first.
   *
   * @type {number}
   */
  this.priority = opt_priority || 0;

  /**
   * The immediate children of this RenderNode.
   *
   * Each access to this field gets the entire list so it is best to get it
   * just once. For example:
   *
   * var children = renderNode.children;
   * for (var i = 0; i < children.length; i++) {
   *   var child = children[i];
   * }
   *
   * Note that modifications to this array [e.g. push()] will not affect
   * the underlying RenderNode, while modifications to the array's members
   * will affect them.
   *
   * @type {!Array.<o3d.RenderNode>}
   */
  this.children = [];

  /**
   * Setting false skips this render node. Setting true processes this render
   * node. (ie, renders whatever it's supposed to render)
   *
   * @type {boolean}
   */
  this.active = opt_active || true;

  /**
   * Sets the parent of the node by re-parenting the node under parent_node.
   * Setting parent_node to null removes the node and the entire subtree below
   * it from the render graph.
   *
   * @type {o3d.RenderNode}
   */
  this.parent = null;
};
o3d.inherit('RenderNode','ParamObject');

o3d.ParamObject.setUpO3DParam_(o3d.RenderNode, 'priority', 'ParamFloat');
o3d.ParamObject.setUpO3DParam_(o3d.RenderNode, 'active', 'ParamBoolean');

o3d.RenderNode.prototype.__defineSetter__('parent',
    function(p) {
      if (this.parent_) {
        this.parent_.removeChild(this);
      }
      this.parent_ = p;
      if (this.parent_) {
        if (!this.parent_.addChild) {
          throw ('Parent of render node must be render node or null.');
        }
        this.parent_.addChild(this);
      }
    }
);

o3d.RenderNode.prototype.__defineGetter__('parent',
    function(p) {
      return this.parent_;
    }
);

/**
 * Adds a child node.
 * @param {o3d.RenderNode} child The child to add.
 */
o3d.RenderNode.prototype.addChild = function(child) {
  this.children.push(child);
};


/**
 * Removes a child node.
 * @param {o3d.RenderNode} child The child to add.
 */
o3d.RenderNode.prototype.removeChild = function(child) {
  o3d.removeFromArray(this.children, child);
};


/**
 * Returns this render node and all its descendants. Note that this render node
 * might not be in the render graph.
 *
 * Note that modifications to this array [e.g. push()] will not affect
 * the underlying RenderNode, while modifications to the array's members
 * will affect them.
 *
 * An array containing all render nodes of the subtree.
 */
o3d.RenderNode.prototype.getRenderNodesInTree =
    function() {
  o3d.notImplemented();
};



/**
 * Searches for render nodes that match the given name in the hierarchy under
 * and including this render node. Since there can be several render nodes
 * with a given name the results are returned in an array.
 *
 * Note that modifications to this array [e.g. push()] will not affect
 * the underlying RenderNode, while modifications to the array's members
 * will affect them.
 *
 * @param {string} name Rendernode name to look for.
 * @return {Array.<!o3d.RenderNode>}  An array containing all nodes among
 *     this node and its decendants that have the given name.
 */
o3d.RenderNode.prototype.getRenderNodesByNameInTree =
    function(name) {
  o3d.notImplemented();
};


/**
 * Searches for render nodes that match the given class name in the hierarchy
 * under and including this render node.
 *
 * Note that modifications to this array [e.g. push()] will not affect
 * the underlying RenderNode, while modifications to the array's members
 * will affect them.
 *
 * @param {string} class_name class name to look for.
 * @return {Array.<!o3d.RenderNode>}  An array containing all nodes among
 *     this node and its decendants whose type is class_name.
 */
o3d.RenderNode.prototype.getRenderNodesByClassNameInTree =
    function(class_name) {
  o3d.notImplemented();
};


/**
 * Recursively traverses the render graph starting at this node.
 */
o3d.RenderNode.prototype.render = function() {
  function compare(a, b) {
    return a.priority - b.priority;
  }
  this.children.sort(compare);
  var children = this.children;

  this.before();
  for (var i = 0; i < children.length; ++i) {
    children[i].render();
  }
  this.after();
};


/**
 * Called during the rendergraph traversal before the children are rendered.
 */
o3d.RenderNode.prototype.before = function() { };


/**
 * Called during the rendergraph traversal after the children are rendered.
 */
o3d.RenderNode.prototype.after = function() { };


/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * A ClearBuffer is a render node that clears the color buffer, zbuffer and/or
 * stencil buffer of the current render target.
 *
 * @constructor
 */
o3d.ClearBuffer = function() {
  o3d.RenderNode.call(this);
  /**
   * The color to clear the buffer in RGBA Float4 format.
   * @type {!o3d.Float4}
   */
  this.clearColor = [0, 0, 0, 1];

  /**
   * true clears the current render target's color buffer to the clear color.
   * false does not clear the color buffer.
   * @type {boolean}
   */
  this.clearColorFlag = true;

  /**
   * The value to clear the depth buffer (0.0 - 1.0)
   * @type {number}
   */
  this.clearDepth = 1;

  /**
   * true clears the current render target's depth buffer to the clear depth
   * value. false does not clear the depth buffer.
   * @type {boolean}
   */
  this.clearDepthFlag = true;

  /**
   * The value to clear the stencil buffer to (0 - 255).
   * @type {number}
   */
  this.clearStencil = 0;

  /**
   * true clears the current render target's stencil buffer to the clear stencil
   * value. false does not clear the stencil buffer
   * @type {boolean}
   */
  this.clearStencilFlag = true;
};
o3d.inherit('ClearBuffer', 'RenderNode');

o3d.ParamObject.setUpO3DParam_(o3d.ClearBuffer, 'clearColor', 'ParamFloat4');
o3d.ParamObject.setUpO3DParam_(o3d.ClearBuffer,
                               'clearColorFlag', 'ParamBoolean');
o3d.ParamObject.setUpO3DParam_(o3d.ClearBuffer, 'clearDepth', 'ParamFloat');
o3d.ParamObject.setUpO3DParam_(o3d.ClearBuffer,
                               'clearDepthFlag', 'ParamBoolean');
o3d.ParamObject.setUpO3DParam_(o3d.ClearBuffer,
                               'clearStencil', 'ParamInteger');
o3d.ParamObject.setUpO3DParam_(o3d.ClearBuffer,
                               'clearStencilFlag', 'ParamBoolean');

/**
 * Function called in the render graph traversal before the children are
 * rendered.
 */
o3d.ClearBuffer.prototype.before = function() {
  var flags = 0;

  this.gl.clearColor(
      this.clearColor[0],
      this.clearColor[1],
      this.clearColor[2],
      this.clearColor[3]);

  this.gl.clearDepth(this.clearDepth);
  this.gl.clearStencil(this.clearStencil);

  if (this.clearColorFlag)
    flags = flags | this.gl.COLOR_BUFFER_BIT;
  if (this.clearDepthFlag)
    flags = flags | this.gl.DEPTH_BUFFER_BIT;
  if (this.clearStencilFlag)
    flags = flags | this.gl.STENCIL_BUFFER_BIT;

  this.gl.clear(flags);
};



/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * A StateSet is a render node that sets render states of all of its
 * children. You can make this a parent of a part of the render graph to set
 * render states in a more global way.
 *
 * @param {o3d.State} opt_state The State the defines what states to set.
 * @constructor
 */
o3d.StateSet = function(opt_state) {
  o3d.RenderNode.call(this);

  /**
   * The State for this StateSet.
   * @type {o3d.State}
   */
  this.state = opt_state || null;
};
o3d.inherit('StateSet', 'RenderNode');

o3d.ParamObject.setUpO3DParam_(o3d.StateSet, 'state', 'ParamState');

/**
 * Sets the current state to the member state.
 */
o3d.StateSet.prototype.before = function() {
  if (this.state) {
    this.state.push_();
  }
};

/**
 * Sets the current state to the member state.
 */
o3d.StateSet.prototype.after = function() {
  if (this.state) {
    this.state.pop_();
  }
};
/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * A Viewport is a render node that sets the render viewport and depth range
 * for its children.  It uses an array in the format [left, top, width, height]
 * where left, top, width and height are in a 0.0 to 1.0 range that represent
 * positions and dimensions relative to the size of the client's rendering
 * area. The depth range is represented by an array in the format
 * [min Z, max Z]. The depth range provides the mapping of the clip space
 * coordinates into normalized z buffer coordinates.
 *
 * @param {o3d.math.Float4} viewport The viewport setting.
 * @param {o3d.math.Float2} depthRange ParamFloat2 The depth range setting.
 * @constructor
 */
o3d.Viewport = function(opt_viewport, opt_depthRange) {
  o3d.RenderNode.call(this);

  /**
   * The position and size to set the viewport in
   * [left, top, width, height] format.
   *
   * Note: These values must describe a rectangle that is 100% inside
   * the client area. In other words, [0.5, 0.0, 1.0, 1.0] would
   * describe an area that is 1/2 off right side of the screen. That
   * is an invalid value and will be clipped to [0.5, 0.0, 0.5, 1.0].
   *
   * Default = [0.0, 0.0, 1.0, 1.0]. In other words, the full area.
   *
   * @type {!Array.<number>}
   */
  this.viewport = opt_viewport || [0.0, 0.0, 1.0, 1.0];

  /**
   * The min Z and max Z depth range in [min Z, max Z] format.
   * Default = [0.0, 1.0].
   *
   * @type {!Array.<number>}
   */
  this.depthRange = opt_depthRange || [0.0, 1.0];
};
o3d.inherit('Viewport', 'RenderNode');

o3d.ParamObject.setUpO3DParam_(o3d.Viewport, 'viewport', 'ParamFloat4');
o3d.ParamObject.setUpO3DParam_(o3d.Viewport, 'depthRange', 'ParamFloat2');

/**
 * Called before the children are rendered.  Sets up a viewport and
 * scissor region in gl.
 */
o3d.Viewport.prototype.before = function() {
  var x = this.viewport[0] * this.gl.displayInfo.width;
  var y = (1 - this.viewport[1] - this.viewport[3]) *
      this.gl.displayInfo.height;
  var width = this.viewport[2] * this.gl.displayInfo.width;
  var height = this.viewport[3] * this.gl.displayInfo.height;

  this.gl.viewport(x, y, width, height);
  if (x != 0 || y != 0 || this.viewport[2] != 1 || this.viewport[3] != 1) {
    this.gl.enable(this.gl.SCISSOR_TEST);
    this.gl.scissor(x, y, width, height);
  } else {
     this.gl.disable(this.gl.SCISSOR_TEST);
  }
};

/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * A TreeTraversal has multiple DrawLists registered with it. Each DrawList has
 * a DrawContext registered with it. At render time the TreeTraversal walks the
 * transform graph from the transform it's pointing at and for each DrawElement
 * it finds who's matertial matches one of its registered DrawLists it adds that
 * DrawElement to that DrawList.
 *
 * @param {o3d.Transform} opt_transform The root transform to start traversing
 *     by this TreeTraveral.
 * @constructor
 */
o3d.TreeTraversal = function(opt_transform) {
  o3d.RenderNode.call(this);

  /**
   * The root Transform this TreeTraversal will start traversing from.
   */
  this.transform = opt_transform || null;

  /**
   * Private list of registered drawlists.
   * @private
   */
  this.drawLists_ = [];

  /**
   * Private list of drawlists to reset at render-time before traversal.
   * @private
   */
  this.drawListsToReset_ = [];
};
o3d.inherit('TreeTraversal', 'RenderNode');

o3d.ParamObject.setUpO3DParam_(o3d.TreeTraversal,
                               'transform', 'ParamTransform');

/**
 * Registers a DrawList with this TreeTraversal so that when this
 * TreeTraversal traverses its tree, DrawElements using materials that use
 * this DrawList will be added though possibly culled by the view frustum of
 * the DrawContext. Note: passing in the same DrawList more than once will
 * override the previous settings for that DrawList.
 * @param {o3d.DrawList} draw_list DrawList to register.
 * @param {o3d.DrawContext} draw_context DrawContext to use with the DrawList.
 * @param {boolean} reset true if you want the DrawList reset before
 *     traversing.
 */
o3d.TreeTraversal.prototype.registerDrawList =
    function(draw_list, draw_context, reset) {
  if (reset == undefined || reset) {
    this.drawListsToReset_.push(draw_list);
  }
  this.drawLists_.push({
    list:draw_list,
    context:draw_context});
};


/**
 * Unregisters a DrawList with this TreeTraversal.
 * @param {o3d.DrawList} draw_list DrawList to unregister.
 * @return {boolean}  true if unregistered. false if this draw_list was
 *     not registered.
 */
o3d.TreeTraversal.prototype.unregisterDrawList =
    function(draw_list) {
  o3d.notImplemented();
};


/**
 * Called in the render graph traversal before the children are rendered.
 */
o3d.TreeTraversal.prototype.before =
    function() {
  for(var i = 0; i < this.drawListsToReset_.length; ++i) {
    this.drawListsToReset_[i].list_ = [];
  }
  this.transform.traverse(this.drawLists_);
};
/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * A DrawList gets used during rendering to collect DrawElements to
 * render.  Each Material references a DrawList.  Depending on the material, as
 * DrawElements get collected they will be put on different DrawLists.
 * @constructor
 */
o3d.DrawList = function() {
  o3d.NamedObject.call(this);
  this.list_ = [];
};
o3d.inherit('DrawList', 'NamedObject');

/**
 * Private list to actually hold the DrawElements
 * @type {!Array.<!Object>}
 */
this.list_ = [];


/**
 * @type {number}
 */
o3d.DrawList.SortMethod = goog.typedef;


/**
 *  SortMethod,
 *    BY_PERFORMANCE
 *    BY_Z_ORDER
 *    BY_PRIORITY
 *
 * Method to sort DrawList by.
 */
o3d.DrawList.BY_PERFORMANCE = 0;
o3d.DrawList.BY_Z_ORDER = 1;
o3d.DrawList.BY_PRIORITY = 2;


/**
 * Compare function for by-priority sort.
 */
o3d.DrawList.comparePriority_ = function(drawElementInfoA, drawElementInfoB) {
  return drawElementInfoA.drawElement.owner.priority -
         drawElementInfoB.drawElement.owner.priority;
};


/**
 * Compare function for by-z-coordinate sort.
 * @param {!o3d.DrawElement} drawElementInfoA
 * @param {!o3d.DrawElement} drawElementInfoB
 */
o3d.DrawList.compareZ_ = function(drawElementInfoA, drawElementInfoB) {
  return o3d.Transform.transformPointZOnly(
            drawElementInfoB.worldViewProjection,
            drawElementInfoB.drawElement.owner.zSortPoint) -
         o3d.Transform.transformPointZOnly(
            drawElementInfoA.worldViewProjection,
            drawElementInfoA.drawElement.owner.zSortPoint);
};


/**
 * Sorts this list according to the given sort method.
 * @param {o3d.DrawList.SortMethod} sort_method Which method to use.
 * @private
 */
o3d.DrawList.prototype.sort_ = function(sort_method) {
  switch (sort_method) {
    case o3d.DrawList.BY_PRIORITY:
      this.list_.sort(o3d.DrawList.comparePriority_);
      break;

    case o3d.DrawList.BY_Z_ORDER:
      this.list_.sort(o3d.DrawList.compareZ_);
      break;

    case o3d.DrawList.BY_PERFORMANCE:
    default:
      break;
  }
};


/**
 * Renders the draw list.
 */
o3d.DrawList.prototype.render = function() {
  for (var i = 0; i < this.list_.length; ++i) {
    var drawElementInfo = this.list_[i];
    var world = drawElementInfo.world;
    var view = drawElementInfo.view;
    var viewProjection = drawElementInfo.viewProjection;
    var worldViewProjection = drawElementInfo.worldViewProjection;
    var projection = drawElementInfo.projection;
    var transform = drawElementInfo.transform;
    var drawElement = drawElementInfo.drawElement;
    var element = drawElement.owner;
    var material = drawElement.material || element.material;
    var effect = material.effect;

    o3d.Param.SAS.setWorld(world);
    o3d.Param.SAS.setView(view);
    o3d.Param.SAS.setProjection(projection);
    o3d.Param.SAS.setViewProjection(viewProjection);
    o3d.Param.SAS.setWorldViewProjection(worldViewProjection);

    var paramObjects = [
      transform,
      drawElement,
      element
    ];
    if (element.streamBank) {
      paramObjects.push(element.streamBank);
    }
    paramObjects.push(
      material,
      effect,
      o3d.Param.SAS);

    effect.searchForParams_(paramObjects);

    var state_on = (material.state != undefined);
    if (state_on) {
      material.state.push_();
    }
    element.render();
    if (state_on) {
      material.state.pop_();
    }
  }
};


/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * A DrawPass renders a DrawList.
 *
 * @param {o3d.DrawList} drawList The DrawList used by this DrawPass.
 * @param {o3d.DrawPass.SortMethod} sortMethod ParamInteger The method
 *     of sorting this DrawPass.
 * @constructor
 */
o3d.DrawPass = function(opt_drawList, opt_sortMethod) {
  o3d.RenderNode.call(this);

  /**
   * The DrawList for this DrawPass.
   * @type {o3d.DrawList}
   */
  this.drawList = opt_drawList || null;

  /**
   * The sort method for this DrawPass to draw the DrawList by.
   * Default = BY_PERFORMANCE.
   * @type {o3d.DrawList.SortMethod}
   */
  this.sortMethod = opt_sortMethod || o3d.DrawList.BY_PERFORMANCE;
};
o3d.inherit('DrawPass', 'RenderNode');

/**
 * @type {number}
 */
o3d.DrawPass.SortMethod = goog.typedef;

o3d.ParamObject.setUpO3DParam_(o3d.DrawPass, 'drawList', 'ParamDrawList');
o3d.ParamObject.setUpO3DParam_(o3d.DrawPass, 'sortMethod', 'ParamInteger');

/**
 * Called in rendergraph traversal before children are rendered.
 */
o3d.DrawPass.prototype.before = function() {
  if (this.drawList) {
    this.drawList.sort_(this.sortMethod);
    this.drawList.render();
  }
};


/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * A RenderSurfaceSet node will bind depth and color RenderSurface nodes
 * to the current rendering context.  All RenderNodes descending
 * from the given RenderSurfaceSet node will operate on the contents of
 * the bound depth and color buffers.
 * Note the following usage constraints of this node:
 * 1)  If both a color and depth surface is bound, then they must be of
 *     matching dimensions.
 * 2)  At least one of render_surface and render_depth_surface must non-null.
 *
 * @param {o3d.RenderSurface} opt_renderSurface The render surface to set.
 * @param {o3d.RenderDepthStencilSurface} opt_renderDepthStencilSurface The
 *     depth stencil render surface to set.
 * @constructor
 */
o3d.RenderSurfaceSet =
    function(opt_renderSurface, opt_renderDepthStencilSurface) {
  o3d.RenderNode.call(this);

  /**
   * The render surface to which the color contents of all RenderNode children
   * should be drawn.
   * @type {o3d.RenderSurface}
   */
  this.renderSurface = opt_renderSurface || null;


  /**
   * The render depth stencil surface to which the depth contents of all
   * RenderNode children should be drawn.
   * @type {o3d.RenderDepthStencilSurface}
   */
  this.renderDepthStencilSurface = opt_renderDepthStencilSurface || null;
};
o3d.inherit('RenderSurfaceSet', 'RenderNode');


o3d.ParamObject.setUpO3DParam_(o3d.RenderSurfaceSet,
                               'renderSurface', 'ParamRenderSurface');
o3d.ParamObject.setUpO3DParam_(o3d.RenderSurfaceSet,
                               'renderDepthStencilSurface',
                               'ParamRenderDepthStencilSurface');

/**
 * Helper function to set the framebuffer back to the default one.
 * @private
 */
o3d.RenderSurfaceSet.prototype.clearFramebufferObjects_ =
    function() {
  this.gl.bindFramebuffer(
      this.gl.FRAMEBUFFER, this.renderSurface.framebuffer_);

  this.gl.framebufferRenderbuffer(
      this.gl.FRAMEBUFFER,
      this.gl.COLOR_ATTACHMENT0,
      this.gl.RENDERBUFFER,
      null);

  this.gl.framebufferRenderbuffer(
      this.gl.FRAMEBUFFER,
      this.gl.DEPTH_ATTACHMENT,
      this.gl.RENDERBUFFER,
      null);

  this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
};


/**
 * Helper function to set up both the color and depth-stencil targets.
 * @private
 */
o3d.RenderSurfaceSet.prototype.installFramebufferObjects_ =
    function() {
  // Reset the bound attachments to the current framebuffer object.
  this.clearFramebufferObjects_();

  this.gl.bindFramebuffer(
      this.gl.FRAMEBUFFER, this.renderSurface.framebuffer_);

  if (this.renderSurface) {
    var texture = this.renderSurface.texture.texture_;
    var level = this.renderSurface.level;

    // TODO(petersont): If it's a cube, this call should be different.
    this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
    this.gl.framebufferTexture2D(
        this.gl.FRAMEBUFFER,
        this.gl.COLOR_ATTACHMENT0,
        this.gl.TEXTURE_2D,
        texture,
        level);
  }

  if (this.renderDepthStencilSurface) {
    var depth_stencil_buffer =
        this.renderDepthStencilSurface.depth_stencil_buffer_;

    // TODO(petersont): In the WebGL spec, there is a depth-stencil
    // attachment, but it hasn't been implemented yet, once it is,
    // this should use one of those.
    this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, depth_stencil_buffer);
    this.gl.framebufferRenderbuffer(
        this.gl.FRAMEBUFFER,
        this.gl.DEPTH_ATTACHMENT,
        this.gl.RENDERBUFFER,
        depth_stencil_buffer);
  }
};


/**
 * Called during the rendergraph traversal before the children are rendered.
 * @private
 */
o3d.RenderSurfaceSet.prototype.before = function() {
  this.installFramebufferObjects_();
  this.previousHeight = this.gl.displayInfo.height;
  this.previousWidth = this.gl.displayInfo.width;
  this.previousRenderSurfaceSet = this.gl.currentRenderSurfaceSet;
  this.gl.displayInfo.height = this.renderSurface.height;
  this.gl.displayInfo.width = this.renderSurface.width;
  this.gl.currentRenderSurfaceSet = this;
};


/**
 * Called during the rendergraph traversal after the children are rendered.
 * @private
 */
o3d.RenderSurfaceSet.prototype.after = function() {
  this.clearFramebufferObjects_();
  this.gl.displayInfo.height = this.previousHeight;
  this.gl.displayInfo.width = this.previousWidth;
  // This is consumed in effect.js.
  this.gl.currentRenderSurfaceSet = this.previousRenderSurfaceSet;
};


/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * A RenderSurfaceBase is the base for RenderSurface and
 * RenderDepthStencilSurface.
 *
 * @param {number} width The width of this RenderSurface.
 * @param {number} height The height of this RenderSurface.
 * @param {o3d.Texture} texture The texture of this RenderSurface.
 * @constructor
 */
o3d.RenderSurfaceBase = function(width, height, texture) {
  o3d.ParamObject.call(this);

  /**
   * The width of the surface, in pixels.
   * @type {number}
   */
  this.width = width || 0;

  /**
   * The height of the surface, in pixels.
   * @type {number}
   */
  this.height = height || 0;

  /**
   * The texture in which this surface is contained.
   */
  this.texture = texture || null;

  /**
   * The mip level targeted by this render surface.
   * @type {number}
   */
  this.level = 0;

  /**
   * The underlying GL framebuffer object.
   * @type {WebGLFramebuffer}
   * @private
   */
  this.framebuffer_ = null;

};
o3d.inherit('RenderSurfaceBase', 'ParamObject');

o3d.ParamObject.setUpO3DParam_(o3d.RenderSurfaceBase, 'width', 'ParamInteger');
o3d.ParamObject.setUpO3DParam_(o3d.RenderSurfaceBase, 'height', 'ParamInteger');

/**
 * A RenderSurface encapsulates the notion of a renderable surface.
 * When used in conjunction with a RenderSurfaceSet node in the render graph,
 * the API allows for rendering of primitives to the given surface.
 * RenderSurface objects are not constructable through the Pack API, they may
 * only be accessed through the texture getRenderSurface(...) interfaces.
 * @constructor
 */
o3d.RenderSurface = function() {
  o3d.RenderSurfaceBase.call(this);
};
o3d.inherit('RenderSurface', 'RenderSurfaceBase');


/**
 * Initializes a render surface to render to the given texture.
 * @param {o3d.Texture2D} texture The texture.
 */
o3d.RenderSurface.prototype.initWithTexture = function(texture, level) {
  this.framebuffer_ = this.gl.createFramebuffer();
  this.texture = texture;
  this.level = level;
  this.width = texture.width;
  this.height = texture.height;
};

/**
 * A RenderDepthStencilSurface represents a depth stencil render surface.
 * @constructor
 */
o3d.RenderDepthStencilSurface = function() {
  o3d.RenderSurfaceBase.call(this);

  /**
   * The GL renderbuffer object for the depth / stencil buffer.
   * @type {WebGLRenderbuffer}
   * @private
   */
  this.depth_stencil_buffer_ = null;
};
o3d.inherit('RenderDepthStencilSurface', 'RenderSurfaceBase');


/**
 * Allocates depth and stnecil buffers of the given size.
 * @param {number} width
 * @param {number} height
 * @private
 */
o3d.RenderDepthStencilSurface.prototype.initWithSize_ =
    function(width, height) {
  this.depth_stencil_buffer_ = this.gl.createRenderbuffer();
  this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, this.depth_stencil_buffer_);
  this.gl.renderbufferStorage(
     this.gl.RENDERBUFFER, this.gl.DEPTH_COMPONENT16, width, height);
  this.width = width;
  this.height = height;
};
/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * A State object sets the RenderStates for a particular material or StateSet.
 * @constructor
 */
o3d.State = function() {
  o3d.ParamObject.call(this);

  this.state_params_ = {};

 /**
 * The names types and default values of all the state variables.
 * @type {Object}
 * @private
 */
 o3d.State.stateVariableInfos_ = o3d.State.stateVariableInfos_ || {
    'AlphaBlendEnable':
        {paramType: 'ParamBoolean', defaultValue: false},
    'AlphaComparisonFunction':
        {paramType: 'ParamInteger', defaultValue: o3d.State.CMP_ALWAYS},
    'AlphaReference':
        {paramType: 'ParamFloat', defaultValue: 0},
    'AlphaTestEnable':
        {paramType: 'ParamBoolean', defaultValue: false},
    'BlendAlphaEquation':
        {paramType: 'ParamInteger', defaultValue: o3d.State.BLEND_ADD},
    'BlendEquation':
        {paramType: 'ParamInteger', defaultValue: o3d.State.BLEND_ADD},
    'CCWStencilComparisonFunction':
        {paramType: 'ParamInteger', defaultValue: o3d.State.CMP_ALWAYS},
    'CCWStencilFailOperation':
        {paramType: 'ParamInteger', defaultValue: o3d.State.STENCIL_KEEP},
    'CCWStencilPassOperation':
        {paramType: 'ParamInteger', defaultValue: o3d.State.STENCIL_KEEP},
    'CCWStencilZFailOperation':
        {paramType: 'ParamInteger', defaultValue: o3d.State.STENCIL_KEEP},
    'ColorWriteEnable':
        {paramType: 'ParamInteger', defaultValue: 15},
    'CullMode':
        {paramType: 'ParamInteger', defaultValue: o3d.State.CULL_CW},
    'DestinationBlendAlphaFunction':
        {paramType: 'ParamInteger', defaultValue: o3d.State.BLENDFUNC_ZERO},
    'DestinationBlendFunction':
        {paramType: 'ParamInteger', defaultValue: o3d.State.BLENDFUNC_ZERO},
    'DitherEnable':
        {paramType: 'ParamBoolean', defaultValue: false},
    'FillMode':
        {paramType: 'ParamInteger', defaultValue: o3d.State.SOLID},
    'LineSmoothEnable':
        {paramType: 'ParamBoolean', defaultValue: false},
    'PointSize':
        {paramType: 'ParamFloat', defaultValue: 0},
    'PointSpriteEnable':
        {paramType: 'ParamBoolean', defaultValue: false},
    'PolygonOffset1':
        {paramType: 'ParamFloat', defaultValue: 0},
    'PolygonOffset2':
        {paramType: 'ParamFloat', defaultValue: 0},
    'SeparateAlphaBlendEnable':
        {paramType: 'ParamBoolean', defaultValue: false},
    'SourceBlendAlphaFunction':
        {paramType: 'ParamInteger', defaultValue: o3d.State.BLENDFUNC_ONE},
    'SourceBlendFunction':
        {paramType: 'ParamInteger', defaultValue: o3d.State.BLENDFUNC_ONE},
    'StencilComparisonFunction':
        {paramType: 'ParamInteger', defaultValue: o3d.State.CMP_ALWAYS},
    'StencilEnable':
        {paramType: 'ParamBoolean', defaultValue: false},
    'StencilFailOperation':
        {paramType: 'ParamInteger', defaultValue: o3d.State.STENCIL_KEEP},
    'StencilMask':
        {paramType: 'ParamInteger', defaultValue: 255},
    'StencilPassOperation':
        {paramType: 'ParamInteger', defaultValue: o3d.State.STENCIL_KEEP},
    'StencilReference':
        {paramType: 'ParamInteger', defaultValue: 0},
    'StencilWriteMask':
        {paramType: 'ParamInteger', defaultValue: 255},
    'StencilZFailOperation':
        {paramType: 'ParamInteger', defaultValue: o3d.State.STENCIL_KEEP},
    'TwoSidedStencilEnable':
        {paramType: 'ParamBoolean', defaultValue: false},
    'ZComparisonFunction':
        {paramType: 'ParamInteger', defaultValue: o3d.State.CMP_LESS},
    'ZEnable':
        {paramType: 'ParamBoolean', defaultValue: true},
    'ZWriteEnable':
        {paramType: 'ParamBoolean', defaultValue: true}
  };
};
o3d.inherit('State', 'ParamObject');


/**
 * A private object containing the the state params by name.
 */
o3d.State.prototype.state_params_ = { };


/**
 *  Comparison
 *  CMP_NEVER  (Never)
 *  CMP_LESS  (Less Than)
 *  CMP_EQUAL  (Equal To)
 *  CMP_LEQUAL  (Less Than or Equal To)
 *  CMP_GREATER  (Greater Than)
 *  CMP_NOTEQUAL  (Not Equal To)
 *  CMP_GEQUAL  (Greater Than or Equal To)
 *  CMP_ALWAYS  (Always)
 */
o3d.State.CMP_NEVER = 0;
o3d.State.CMP_LESS = 1;
o3d.State.CMP_EQUAL = 2;
o3d.State.CMP_LEQUAL = 3;
o3d.State.CMP_GREATER = 4;
o3d.State.CMP_NOTEQUAL = 5;
o3d.State.CMP_GEQUAL = 6;
o3d.State.CMP_ALWAYS = 7;



/**
 * type {number}
 */
o3d.Cull = goog.typedef;

/**
 *  Cull
 *  CULL_NONE  Don't Cull by face
 *  CULL_CW  Cull clock-wise faces
 *  CULL_CCW  Cull counter clock-wise faces
 */
o3d.State.CULL_NONE = 0;
o3d.State.CULL_CW = 1;
o3d.State.CULL_CCW = 2;




/**
 *  Fill
 *  POINT
 *  WIREFRAME
 *  SOLID
 */
o3d.State.POINT = 0;
o3d.State.WIREFRAME = 1;
o3d.State.SOLID = 2;



/**
 *  BlendingFunction
 *  BLENDFUNC_ZERO
 *  BLENDFUNC_ONE
 *  BLENDFUNC_SOURCE_COLOR
 *  BLENDFUNC_INVERSE_SOURCE_COLOR
 *  BLENDFUNC_SOURCE_ALPHA
 *  BLENDFUNC_INVERSE_SOURCE_ALPHA
 *  BLENDFUNC_DESTINATION_ALPHA
 *  BLENDFUNC_INVERSE_DESTINATION_ALPHA
 *  BLENDFUNC_DESTINATION_COLOR
 *  BLENDFUNC_INVERSE_DESTINATION_COLOR
 *  BLENDFUNC_SOURCE_ALPHA_SATUTRATE
 */
o3d.State.BLENDFUNC_ZERO = 0;
o3d.State.BLENDFUNC_ONE = 1;
o3d.State.BLENDFUNC_SOURCE_COLOR = 2;
o3d.State.BLENDFUNC_INVERSE_SOURCE_COLOR = 3;
o3d.State.BLENDFUNC_SOURCE_ALPHA = 4;
o3d.State.BLENDFUNC_INVERSE_SOURCE_ALPHA = 5;
o3d.State.BLENDFUNC_DESTINATION_ALPHA = 6;
o3d.State.BLENDFUNC_INVERSE_DESTINATION_ALPHA = 7;
o3d.State.BLENDFUNC_DESTINATION_COLOR = 8;
o3d.State.BLENDFUNC_INVERSE_DESTINATION_COLOR = 9;
o3d.State.BLENDFUNC_SOURCE_ALPHA_SATUTRATE = 10;



/**
 *  BlendingEquation
 *  BLEND_ADD
 *  BLEND_SUBTRACT
 *  BLEND_REVERSE_SUBTRACT
 *  BLEND_MIN
 *  BLEND_MAX
 */
o3d.State.BLEND_ADD = 0;
o3d.State.BLEND_SUBTRACT = 1;
o3d.State.BLEND_REVERSE_SUBTRACT = 2;
o3d.State.BLEND_MIN = 3;
o3d.State.BLEND_MAX = 4;



/**
 *  StencilOperation
 *  STENCIL_KEEP
 *  STENCIL_ZERO
 *  STENCIL_REPLACE
 *  STENCIL_INCREMENT_SATURATE
 *  STENCIL_DECREMENT_SATURATE
 *  STENCIL_INVERT
 *  STENCIL_INCREMENT
 *  STENCIL_DECREMENT
 */
o3d.State.STENCIL_KEEP = 0;
o3d.State.STENCIL_ZERO = 1;
o3d.State.STENCIL_REPLACE = 2;
o3d.State.STENCIL_INCREMENT_SATURATE = 3;
o3d.State.STENCIL_DECREMENT_SATURATE = 4;
o3d.State.STENCIL_INVERT = 5;
o3d.State.STENCIL_INCREMENT = 6;
o3d.State.STENCIL_DECREMENT = 7;



/**
 * Returns a Param for a given state. If the param does not already exist it
 * will be created. If the state_name is invalid it will return null.
 * @param {string} state_name name of the state
 * @return {o3d.Param}  param or null if no matching state.
 *
 * Example:
 *
 *
 * g_o3d = document.o3d.o3d;
 * ...
 *
 * var state = my_pack.createState("my_state");
 *
 *
 * state.getStateParam('StencilEnable').value = true;
 * state.getStateParam('StencilReference').value = 25;
 * state.getStateParam('StencilPassOperation').value =
 *     g_o3d.State.STENCIL_REPLACE;
 * state.getStateParam('StencilComparisonFunction').value =
 *     g_o3d.State.CMP_ALWAYS;
 * state.getStateParam('ZEnable').value = false;
 * state.getStateParam('ZWriteEnable').value = false;
 * state.getStateParam('ColorWriteEnable').value = 0;
 *
 * Valid states:
 *
 * State NameTypeDefault Value
 * o3d.AlphaBlendEnableParamBoolean
 *     default = false
 * o3d.AlphaComparisonFunctionParamInteger,
 *     State.Comparisondefault = State.CMP_ALWAYS
 * o3d.AlphaReferenceParamFloat 0-1
 *     default = 0
 * o3d.AlphaTestEnableParamBoolean
 *     default = false
 * o3d.BlendAlphaEquation
 *     ParamInteger, State.BlendingEquation
 *     default = State.BLEND_ADD
 * o3d.BlendEquation
 *     ParamInteger, State.BlendingEquation
 *     default = State.BLEND_ADD
 * o3d.CCWStencilComparisonFunction
 *     ParamInteger, State.Comparison
 *     default = State.CMP_ALWAYS
 * o3d.CCWStencilFailOperation
 *     ParamInteger, State.StencilOperation
 *     default = State.STENCIL_KEEP
 * o3d.CCWStencilPassOperation
 *     ParamInteger, State.StencilOperation
 *     default = State.STENCIL_KEEP
 * o3d.CCWStencilZFailOperation
 *     ParamInteger, State.StencilOperation
 *     default = State.STENCIL_KEEP
 * o3d.ColorWriteEnable
 *     ParamInteger 0-15 bit 0 = red, bit 1 = green,
 *     bit 2 = blue, bit 3 = alphadefault = 15
 * o3d.CullModeParamInteger, State.Cull
 *     default = State.CULL_CW
 * o3d.DestinationBlendAlphaFunction
 *     ParamInteger, State.BlendingFunction
 *     default = State.BLENDFUNC_ZERO
 * o3d.DestinationBlendFunction
 *     ParamInteger, State.BlendingFunction
 *     default = State.BLENDFUNC_ZERO
 * o3d.DitherEnableParamBoolean
 *     default = false
 * o3d.FillModeParamInteger, State.Fill
 *     default = State.SOLID
 * o3d.LineSmoothEnableParamBoolean
 *     default = false
 * o3d.PointSizeParamFloatTBD
 * o3d.PointSpriteEnableParamBoolean
 *     default = false
 * o3d.PolygonOffset1
 *     ParamFloat, polygon offset slope factor0
 * o3d.PolygonOffset2ParamFloat, polygon offset bias (in
 *     resolvable units)0
 * o3d.SeparateAlphaBlendEnableParamBoolean
 *     default = false
 * o3d.SourceBlendAlphaFunction
 *     ParamInteger, State.BlendingFunction
 *     default = State.BLENDFUNC_ONE
 * o3d.SourceBlendFunction
 *     ParamInteger, State.BlendingFunction
 *     default = State.BLENDFUNC_ONE
 * o3d.StencilComparisonFunction
 *     ParamInteger, State.Comparison
 *     default = State.CMP_ALWAYS
 * o3d.StencilEnableParamBoolean
 *     default = false
 * o3d.StencilFailOperation
 *     ParamInteger, State.StencilOperation
 *     default = State.STENCIL_KEEP
 * o3d.StencilMaskParamInteger 0-255
 *     default = 255
 * o3d.StencilPassOperation
 *     ParamInteger, State.StencilOperation
 *     default = State.STENCIL_KEEP
 * o3d.StencilReferenceParamInteger 0-255
 *     default = 0
 * o3d.StencilWriteMaskParamInteger 0-255
 *     default = 255
 * o3d.StencilZFailOperation
 *     ParamInteger, State.StencilOperation
 *     default = State.STENCIL_KEEP
 * o3d.TwoSidedStencilEnableParamBoolean
 *     default = false
 * o3d.ZComparisonFunction
 *     ParamInteger, State.Comparison
 *     default = State.CMP_LESS
 * o3d.ZEnableParamBoolean
 *     default = true
 * o3d.ZWriteEnableParamBoolean
 *     default = true
 *
 *
 * Note: Polygon offset is computed with the following formula:
 *
 * totalOffset = PolygonOffset1 * slope + PolygonOffset2 * r
 *
 * Slope is the maximum difference in depth between 2 adjacent pixels of the
 * polygon. r is the smallest value that would fail the NOTEQUAL test against
 * 0.
 * Typical useful values to layer a polygon on top of another one are -1.0 for
 * each of PolygonOffset1 and PolygonOffset2.
 */
o3d.State.prototype.getStateParam =
    function(state_name) {
  if (!this.state_params_[state_name]) {
    var info = o3d.State.stateVariableInfos_[state_name];
    var param = new o3d.global.o3d[info.paramType];
    param.value = info.defaultValue;
    this.state_params_[state_name] = param;
  }
  return this.state_params_[state_name];
};


/**
 * Constructs a state to set all state variables to their default value.
 * This is meant to be called once at client initialization.
 * @param {WebGLContext} gl The context associated with the calling client.
 * @return {o3d.State} A new state object.
 * @private
 */
o3d.State.createDefaultState_ = function(gl) {
  var state = new o3d.State;
  state.gl = gl;
  var infos = o3d.State.stateVariableInfos_;
  for (name in infos) {
    var info = infos[name];
    state.getStateParam(name).value = info.defaultValue;
  }
  return state;
};


/**
 * Converts a comparison function type constant from o3d to WebGL.
 * @param {!WebGLContext} gl The current context.
 * @param {number} blend_func The o3d constant.
 * @return {number} The WebGL version of the constant.
 * @private
 */
o3d.State.convertCmpFunc_ = function(gl, cmp) {
  switch(cmp) {
    case o3d.State.CMP_ALWAYS:
      return gl.ALWAYS;
    case o3d.State.CMP_NEVER:
      return gl.NEVER;
    case o3d.State.CMP_LESS:
      return gl.LESS;
    case o3d.State.CMP_GREATER:
      return gl.GREATER;
    case o3d.State.CMP_LEQUAL:
      return gl.LEQUAL;
    case o3d.State.CMP_GEQUAL:
      return gl.GEQUAL;
    case o3d.State.CMP_EQUAL:
      return gl.EQUAL;
    case o3d.State.CMP_NOTEQUAL:
      return gl.NOTEQUAL;
    default:
      break;
  }
  return gl.ALWAYS;
};


/**
 * Converts a blend function type constant from o3d to WebGL.
 * @param {!WebGLContext} gl The current context.
 * @param {number} blend_func The o3d constant.
 * @return {number} The WebGL version of the constant.
 * @private
 */
o3d.State.convertBlendFunc_ = function(gl, blend_func) {
  switch (blend_func) {
    case o3d.State.BLENDFUNC_ZERO:
      return gl.ZERO;
    case o3d.State.BLENDFUNC_ONE:
      return gl.ONE;
    case o3d.State.BLENDFUNC_SOURCE_COLOR:
      return gl.SRC_COLOR;
    case o3d.State.BLENDFUNC_INVERSE_SOURCE_COLOR:
      return gl.ONE_MINUS_SRC_COLOR;
    case o3d.State.BLENDFUNC_SOURCE_ALPHA:
      return gl.SRC_ALPHA;
    case o3d.State.BLENDFUNC_INVERSE_SOURCE_ALPHA:
      return gl.ONE_MINUS_SRC_ALPHA;
    case o3d.State.BLENDFUNC_DESTINATION_ALPHA:
      return gl.DST_ALPHA;
    case o3d.State.BLENDFUNC_INVERSE_DESTINATION_ALPHA:
      return gl.ONE_MINUS_DST_ALPHA;
    case o3d.State.BLENDFUNC_DESTINATION_COLOR:
      return gl.DST_COLOR;
    case o3d.State.BLENDFUNC_INVERSE_DESTINATION_COLOR:
      return gl.ONE_MINUS_DST_COLOR;
    case o3d.State.BLENDFUNC_SOURCE_ALPHA_SATUTRATE:
      return gl.SRC_ALPHA_SATURATE;
    default:
      break;
  }
  return gl.ONE;
};


/**
 * Converts a stencil type constant from o3d to WebGL.
 * @param {!WebGLContext} gl The current context.
 * @param {number} stencil_func The o3d constant.
 * @return {number} The WebGL version of the constant.
 * @private
 */
o3d.State.convertBlendEquation_ = function(gl, blend_equation) {
  switch (blend_equation) {
    case o3d.State.BLEND_ADD:
      return gl.FUNC_ADD;
    case o3d.State.BLEND_SUBTRACT:
      return gl.FUNC_SUBTRACT;
    case o3d.State.BLEND_REVERSE_SUBTRACT:
      return gl.FUNC_REVERSE_SUBTRACT;
    case o3d.State.BLEND_MIN:
      return gl.MIN;
    case o3d.State.BLEND_MAX:
      return gl.MAX;
    default:
      break;
  }
  return gl.FUNC_ADD;
};


/**
 * Sets the internal state to the this state.
 * @private
 */
o3d.State.prototype.push_ = function() {
  this.gl.client.pushState_(this.getVariableMap_());
};


/**
 * Recovers the internal state prior to this state gettings set.
 * @private
 */
o3d.State.prototype.pop_ = function() {
  this.gl.client.popState_();
};


/**
 * Returns a new javascript object of name value pairs indicating
 * what values to set each of the (changing) state variables.
 * @return {!Object} The variable map.
 * @private
 */
o3d.State.prototype.getVariableMap_ = function() {
  var m = {};
  var stateParams = this.state_params_;
  for (var name in stateParams) {
    m[name] = stateParams[name].value;
  }
  return m;
};



/**
 * Helper function for setVariables_, looks for each of the given state
 * variables' names in the given variable map.  If any one of them is a key
 * in the map, it fills in the rest in the target_map with the value either
 * from the variable map or from the state if it isn't on the variable map.
 * @param {Array.<!string>} names The names of the variables in question.
 * @param {!Object} variable_map An object connecting names to values.
 * @param {!Object} target_map An object to fill out with the variables from
 *    the given array of names.
 * @return {boolean} True if any of the variable names in the given array were
 *    found in the variable_map
 * @private
 */
o3d.State.relevantValues_ =
    function(gl, names, variable_map, target_map) {
  var found = false;
  for (var i = 0; i < names.length; ++i) {
    var name = names[i];
    if (variable_map[name] !== undefined) {
      found = true;
      break;
    }
  }

  if (found) {
    for (var i = 0; i < names.length; ++i) {
      var name = names[i];
      var value = variable_map[name];
      if (value === undefined) {
        value = gl.client.getState_(name);
      }
      target_map[name] = value;
    }
  }
  return found;
};


/**
 * Sets the internal state according to the name value pairs in the given
 * object.
 * @param {WebGLContext} gl The gl context to use.
 * @param {Object} variable_map A map linking state variable names to values.
 * @private
 */
o3d.State.setVariables_ = function(gl, variable_map) {
  // TODO(petersont): Only some of the state variables have been implemented.
  // Others are unavailable in webgl.

  // Remember, any state variable might be missing from variable_map.  When the
  // key is not present, the state should be left alone.

  // Temporary map to hold name value pairs.
  var v = {};

  if (this.relevantValues_(gl, ['AlphaBlendEnable'], variable_map, v)) {
    if (v['AlphaBlendEnable']) {
      gl.enable(gl.BLEND);
    } else {
      gl.disable(gl.BLEND);
    }
  }

  if (this.relevantValues_(gl, ['SeparateAlphaBlendEnable',
                                'SourceBlendFunction',
                                'SourceBlendAlphaFunction',
                                'DestinationBlendAlphaFunction',
                                'BlendEquation',
                                'BlendAlphaEquation'], variable_map, v)) {
    if (v['SeparateAlphaBlendEnable']) {
      gl.blendFuncSeparate(
          o3d.State.convertBlendFunc_(gl, v['SourceBlendFunction']),
          o3d.State.convertBlendFunc_(gl, v['DestinationBlendFunction']),
          o3d.State.convertBlendFunc_(gl, v['SourceBlendAlphaFunction']),
          o3d.State.convertBlendFunc_(gl, v['DestinationBlendAlphaFunction']));
      gl.blendEquationSeparate(
        o3d.State.convertBlendEquation_(gl, v['BlendEquation']),
        o3d.State.convertBlendEquation_(gl, v['BlendAlphaEquation']));
    }
  }

  if (this.relevantValues_(gl, ['SourceBlendFunction',
                                'DestinationBlendFunction'], variable_map, v)) {
    gl.blendFunc(
        o3d.State.convertBlendFunc_(gl, v['SourceBlendFunction']),
        o3d.State.convertBlendFunc_(gl, v['DestinationBlendFunction']));
  }

  if (this.relevantValues_(gl, ['BlendEquation'], variable_map, v)) {
    gl.blendEquation(o3d.State.convertBlendEquation_(gl, v['BlendEquation']));
  }

  if (this.relevantValues_(gl, ['CullMode'], variable_map, v)) {
    switch (v['CullMode']) {
      case o3d.State.CULL_CW:
        gl.enable(gl.CULL_FACE);
        gl.cullFace(gl.BACK);
        break;
      case o3d.State.CULL_CCW:
        gl.enable(gl.CULL_FACE);
        gl.cullFace(gl.FRONT);
        break;
      default:
        gl.disable(gl.CULL_FACE);
        break;
    }
  }

  if (this.relevantValues_(gl, ['DitherEnable'], variable_map, v)) {
    if (v['DitherEnable']) {
      gl.enable(gl.DITHER);
    } else {
      gl.disable(gl.DITHER);
    }
  }

  if (this.relevantValues_(gl, ['ZEnable', 'ZComparisonFunction'],
      variable_map, v)) {
    if (v['ZEnable']) {
      gl.enable(gl.DEPTH_TEST);
      gl.depthFunc(
          this.convertCmpFunc_(gl, v['ZComparisonFunction']));
    } else {
      gl.disable(gl.DEPTH_TEST);
    }
  }

  if (this.relevantValues_(gl, ['ZWriteEnable'], variable_map, v)) {
    gl.depthMask(v['ZWriteEnable']);
  }

  if (this.relevantValues_(gl, ['StencilEnable', 'StencilComparisonFunction'],
      variable_map, v)) {
    if (v['StencilEnable']) {
      gl.enable(gl.STENCIL_TEST);
      gl.stencilFunc(
          this.convertCmpFunc_(gl, v['StencilComparisonFunction']));
    } else {
      gl.disable(gl.STENCIL_TEST);
    }
  }

  if (this.relevantValues_(gl, ['PolygonOffset1',
                                'PolygonOffset2'], variable_map, v)) {
    var polygon_offset_factor = v['PolygonOffset1'] || 0;
    var polygon_offset_bias = v['PolygonOffset2'] || 0;

    if (polygon_offset_factor || polygon_offset_bias) {
      gl.enable(gl.POLYGON_OFFSET_FILL);
      gl.polygonOffset(polygon_offset_factor, polygon_offset_bias);
    } else {
      gl.disable(gl.POLYGON_OFFSET_FILL);
    }
  }

  if (this.relevantValues_(gl, ['FillMode'], variable_map, v)) {
    // We emulate the behavior of the fill mode state in the primitive class.
    gl.fillMode_ = v['FillMode'];
  }
};



/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * The DrawContext defines the parameters used for a particular drawing pass.
 * It contains two 4-by-4 matrix params, view and
 * projection. These correspond to the viewing and projection transformation
 * matrices.
 *
 * @param {!o3d.Matrix4} opt_view The view matrix for this DrawContext.
 * @param {!o3d.Matrix4} opt_projection The projection matrix
 *     for this DrawContext.
 * @constructor
 */
o3d.DrawContext = function(opt_view, opt_projection) {
  o3d.ParamObject.call(this);

  /**
   * The view matrix represents the viewing transformation, used to
   * take vertices from world space to view space.
   * @type {o3d.Matrix4}
   */
  this.view = opt_view ||
      [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];

  /**
   * The projection matrix represents the projection transformation,
   * used to take vertices from view space to screen space.  This
   * matrix is usually an orthographic or perspective transformation.
   * @type {o3d.Matrix4}
   */
  this.projection = opt_projection ||
      [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];
};
o3d.inherit('DrawContext', 'ParamObject');

o3d.ParamObject.setUpO3DParam_(o3d.DrawContext, 'view', 'ParamMatrix4');
o3d.ParamObject.setUpO3DParam_(o3d.DrawContext, 'projection', 'ParamMatrix4');

/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * A RayIntersectionInfo is used to return the results of ray intersection
 * tests.
 * @constructor
 */
o3d.RayIntersectionInfo = function() {
  o3d.NamedObject.call(this);
  o3d.RayIntersectionInfo.prototype.position = [0, 0, 0];
};
o3d.inherit('RayIntersectionInfo', 'NamedObject');


/**
 * True if this ray intersection info is valid. For example if you call
 * element.intersectRay on an element that has no vertex buffers the result
 * will be invalid.
 * @type {boolean}
 */
o3d.RayIntersectionInfo.prototype.valid = false;


/**
 * True if the origin of the ray is found to be inside the box.
 * @type {boolean}
 */
o3d.RayIntersectionInfo.prototype.inside = false;


/**
 * True if this ray intersection intersected something.
 * @type {boolean}
 */
o3d.RayIntersectionInfo.prototype.intersected = false;



/**
 * The position the ray intersected something.
 * type {!o3d.Point3}
 */
o3d.RayIntersectionInfo.prototype.position = [0, 0, 0];



/**
 * The index of the primitive that was intersected.
 * @type {number}
 */
o3d.RayIntersectionInfo.prototype.primitiveIndex = -1;



/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * Sampler is the base of all texture samplers.  Texture samplers encapsulate
 * a texture reference with a set of states that define how the texture
 * gets applied to a surface.  Sampler states are set either via Params defined
 * on the Sampler object or directly via one the convenience methods defined
 * on the Sampler.  The following states are supported (default values are in
 * parenthesis):
 *  \li 'addressModeU' (WRAP)
 *  \li 'addressModeV' (WRAP)
 *  \li 'addressModeW' (WRAP)
 *  \li 'magFilter' (LINEAR)
 *  \li 'minFilter' (LINEAR)
 *  \li 'mipFilter' (POINT)
 *  \li 'borderColor' ([0,0,0,0])
 *  \li 'maxAnisotropy' (1)
 */
o3d.Sampler = function() {
  o3d.ParamObject.call(this);

  /**
   * The texture address mode for the u coordinate.
   * @type {!o3d.Sampler.AddressMode}
   */
  this.addressModeU = o3d.Sampler.WRAP;

  /**
   * The texture address mode for the v coordinate.
   * @type {!o3d.Sampler.AddressMode}
   */
  this.addressModeV = o3d.Sampler.WRAP;

  /**
   * The texture address mode for the w coordinate.
   * @type {!o3d.Sampler.AddressMode}
   */
  this.addressModeW = o3d.Sampler.WRAP;

  /**
   * The magnification filter.  Valid values for the mag filter are:
   * POINT and LINEAR. Default = LINEAR.
   * @type {!o3d.Sampler.FilterType}
   */
  this.magFilter = o3d.Sampler.LINEAR;

  /**
   * The minification filter. Valid values for the min filter are:
   * POINT, LINEAR and ANISOTROPIC. Default = LINEAR.
   * @type {!o3d.Sampler.FilterType}
   */
  this.minFilter = o3d.Sampler.LINEAR;

  /**
   * The mipmap filter used during minification.  Valid values for the
   * mip filter are: NONE, POINT and LINEAR. Default = LINEAR.
   * @type {!o3d.Sampler.FilterType}
   */
  this.mipFilter = o3d.Sampler.LINEAR;

  /**
   * Color returned for texture coordinates outside the [0,1] range when the
   * address mode is set to BORDER.
   * @type {!Array.<number>}
   */
  this.borderColor = [0, 0, 0, 0];

  /**
   * Degree of anisotropy used when the ANISOTROPIC filter type is used.
   * @type {number}
   */
  this.maxAnisotropy = 1;

  /**
   * The Texture object used by this Sampler.
   * @type {o3d.Texture}
   */
  this.texture = null;
};
o3d.inherit('Sampler', 'ParamObject');



/**
 * @type {number}
 */
o3d.Sampler.AddressMode = goog.typedef;


/**
 *  AddressMode,
 *   Controls what happens with texture coordinates outside the [0..1] range.
 *  WRAP
 *  MIRROR
 *  CLAMP
 *  BORDER
 */
o3d.Sampler.WRAP = 0;
o3d.Sampler.MIRROR = 1;
o3d.Sampler.CLAMP = 2;
o3d.Sampler.BORDER = 3;


/**
 * @type {number}
 */
o3d.Sampler.FilterType = goog.typedef;

/**
 *  FilterType,
 *   Texture filtering types.
 *  NONE
 *  POINT
 *  LINEAR
 *  ANISOTROPIC
 */
o3d.Sampler.NONE = 0;
o3d.Sampler.POINT = 1;
o3d.Sampler.LINEAR = 2;
o3d.Sampler.ANISOTROPIC = 3;


o3d.ParamObject.setUpO3DParam_(o3d.Sampler, 'addressModeU', 'ParamInteger');
o3d.ParamObject.setUpO3DParam_(o3d.Sampler, 'addressModeV', 'ParamInteger');
o3d.ParamObject.setUpO3DParam_(o3d.Sampler, 'addressModeW', 'ParamInteger');
o3d.ParamObject.setUpO3DParam_(o3d.Sampler, 'magFilter', 'ParamInteger');
o3d.ParamObject.setUpO3DParam_(o3d.Sampler, 'minFilter', 'ParamInteger');
o3d.ParamObject.setUpO3DParam_(o3d.Sampler, 'mipFilter', 'ParamInteger');
o3d.ParamObject.setUpO3DParam_(o3d.Sampler, 'borderColor', 'ParamFloat4');
o3d.ParamObject.setUpO3DParam_(o3d.Sampler, 'maxAnisotropy', 'ParamInteger');
o3d.ParamObject.setUpO3DParam_(o3d.Sampler, 'texture', 'ParamTexture');


/**
 * Converts the addressing mode of the sampler from an o3d constant to a webgl
 * constant.
 * @param {!o3d.Sampler.AddressMode} o3d_mode, the O3D addressing mode.
 * @return {number} The webgl mode.
 */
o3d.Sampler.prototype.convertAddressMode_ = function(o3d_mode) {
  var gl_mode = this.gl.REPEAT;
  switch (o3d_mode) {
    case o3d.Sampler.WRAP:
      gl_mode = this.gl.REPEAT;
      break;
    case o3d.Sampler.MIRROR:
      gl_mode = this.gl.MIRRORED_REPEAT;
      break;
    case o3d.Sampler.CLAMP:
      gl_mode = this.gl.CLAMP_TO_EDGE;
      break;
    case o3d.Sampler.BORDER:
      // This is not supported in WebGL.
    default:
      this.gl.client.error_callback("Unknown/Unavailable Address mode");
      break;
  }
  return gl_mode;
}


/**
 * Converts the min filter mode of the sampler from an o3d constant to a webgl
 * constant.
 * @param {!o3d.Sampler.FilterType} o3d_filter, the O3D filter.
 * @param {!o3d.Sampler.FilterType} mip_filter, the O3D mip filter.
 * @return {number} The webgl filter.
 */
o3d.Sampler.prototype.convertMinFilter_ = function(o3d_filter, mip_filter) {
  switch (o3d_filter) {
    case o3d.Sampler.NONE:
      return this.gl.NEAREST;
    case o3d.Sampler.POINT:
      if (mip_filter == o3d.Sampler.NONE) {
        return this.gl.NEAREST;
      } else if (mip_filter == o3d.Sampler.POINT) {
        return this.gl.NEAREST_MIPMAP_NEAREST;
      } else if (mip_filter == o3d.Sampler.LINEAR) {
        return this.gl.NEAREST_MIPMAP_LINEAR;
      } else if (mip_filter == o3d.Sampler.ANISOTROPIC) {
        return this.gl.NEAREST_MIPMAP_LINEAR;
      }
    case o3d.Sampler.ANISOTROPIC:
    case o3d.Sampler.LINEAR:
      if (mip_filter == o3d.Sampler.NONE) {
        return this.gl.LINEAR;
      } else if (mip_filter == o3d.Sampler.POINT) {
        return this.gl.LINEAR_MIPMAP_NEAREST;
      } else if (mip_filter == o3d.Sampler.LINEAR) {
        return this.gl.LINEAR_MIPMAP_LINEAR;
      } else if (mip_filter == o3d.Sampler.ANISOTROPIC) {
        return this.gl.LINEAR_MIPMAP_LINEAR;
      }
  }

  this.gl.client.error_callback("Unknown filter.");
  return this.gl.NONE;
}


/**
 * Converts the mag filter mode of the sampler from an o3d constant to a webgl
 * constant.
 * @param {!o3d.Sampler.FilterType} o3d_filter, the O3D filter.
 * @return {number} The webgl filter.
 */
o3d.Sampler.prototype.convertMagFilter_ = function(o3d_filter) {
  switch (o3d_filter) {
    case o3d.Sampler.NONE:
    case o3d.Sampler.POINT:
      return this.gl.NEAREST;
    case o3d.Sampler.LINEAR:
    case o3d.Sampler.ANISOTROPIC:
      return this.gl.LINEAR;
  }
  this.gl.client.error_callback("Unknown filter.");
  return this.gl.LINEAR;
}


/**
 * A default Sampler that has no texture, thus uses the client's error texture.
 *
 * @type {!o3d.Sampler}
 * @private
 */
o3d.Sampler.defaultSampler_ = new o3d.Sampler();
o3d.Sampler.defaultSampler_.magFilter = o3d.Sampler.POINT;

/**
 * Binds the texture for this sampler and sets texParameters according to the
 * states of the sampler.
 * @param {boolean} opt_isCube Optional boolean indicating if this is a cube
 *     map, so we can use the right error texture.
 */
o3d.Sampler.prototype.bindAndSetParameters_ = function(opt_isCube) {
  var currentTexture = null;
  if (this.texture) {
    currentTexture = this.texture;
  } else if (!this.gl.client.reportErrors_()) {
    if (opt_isCube) {
      currentTexture = this.gl.client.error_texture_cube_;
    } else {
      currentTexture = this.gl.client.error_texture_;
    }
  } else {
    currentTexture = this.gl.client.fallback_error_texture_;
    this.gl.client.error_callback("Missing texture for sampler " + this.name);
  }

  var mip_filter = this.mipFilter;
  if (currentTexture.levels == 1) {
    mip_filter = o3d.Sampler.NONE;
  }
  currentTexture.bindAndSetParameters_(
    this.convertAddressMode_(this.addressModeU),
    this.convertAddressMode_(this.addressModeV),
    this.convertMinFilter_(this.minFilter, mip_filter),
    this.convertMagFilter_(this.magFilter));
}

/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * The Transform defines parent child relationship and a localMatrix..
 * A Transform can have one or no parents and
 * an arbitrary number of children.
 *
 * @param {o3d.math.Matrix4} opt_localMatrix The local matrix for this
 *     transform.
 * @param {o3d.math.Matrix4} opt_worldMatrix ParamMatrix4 The world matrix of
 *     this transform.
 * @param {boolean} opt_visible Whether or not this transform and all its
 *     children are visible.
 * @param {o3d.BoundingBox} opt_boundingBox ParamBoundingBox The bounding box
 *     for this transform and all its children.
 * @param {boolean} opt_cull ParamBoolean Whether or not to attempt to
 *    cull this transform and all its children based on whether or not its
 *    bounding box is in the view frustum.
 * @constructor
 */
o3d.Transform =
    function(opt_localMatrix, opt_worldMatrix, opt_visible, opt_boundingBox,
             opt_cull) {
  o3d.ParamObject.call(this);

  /**
   * The local BoundingBox for this Transform. This is the bounding box before
   * the Transform's local matrix has been applied to it.
   * @type {!o3d.BoundingBox}
   */
  this.boundingBoxLocal = opt_boundingBox || new o3d.BoundingBox();

  /**
   * The BoundingBox for this Transform. If culling is on this
   * bounding box will be tested against the view frustum of any draw
   * context used to with this Transform.
   * @type {!o3d.BoundingBox}
   */
  this.boundingBox = null; // Will be set immediately with the local matrix

  /**
   * Local transformation matrix.
   * Default = Identity.
   */
  this.localMatrix = opt_localMatrix ||
      [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];

  /**
   * World (model) matrix as it was last computed.
   */
  this.worldMatrix = opt_worldMatrix ||
      [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];

  /**
   * Sets the parent of the transform by re-parenting the transform under
   * parent. Setting parent to null removes the transform and the
   * entire subtree below it from the transform graph.
   * If the operation would create a cycle it fails.
   */
  this.parent = null;

  /**
   * The Visibility for this transform.
   * Default = true.
   */
  this.visible = opt_visible || true;

  /**
   * The cull setting for this transform. If true this Transform will
   * be culled by having its bounding box compared to the view frustum
   * of any draw context it is used with.
   * Default = false.
   */
  this.cull = opt_cull || false;

  /**
   * The immediate children of this Transform.
   *
   * Each access to this field gets the entire list, so it is best to get it
   * just once. For example:
   *
   * var children = transform.children;
   * for (var i = 0; i < children.length; i++) {
   *   var child = children[i];
   * }
   *
   * Note that modifications to this array [e.g. additions to it] will
   * not affect the underlying Transform, while modifications to the
   * members of the array will affect them.
   */
  this.children = [];

  /**
   * Gets the shapes owned by this transform.
   *
   * Each access to this field gets the entire list so it is best to get it
   * just once. For example:
   *
   * var shapes = transform.shapes;
   * for (var i = 0; i < shapes.length; i++) {
   *   var shape = shapes[i];
   * }
   *
   *
   * Note that modifications to this array [e.g. additions to it] will
   * not affect the underlying Transform, while modifications to the
   * members of the array will affect them.
   */
  this.shapes = [];
};
o3d.inherit('Transform', 'ParamObject');

o3d.ParamObject.setUpO3DParam_(o3d.Transform, 'visible', 'ParamBoolean');
// TODO(petersont): need to better understand and possibly implement
// the semantics of SlaveParamMatrix4.
o3d.ParamObject.setUpO3DParam_(o3d.Transform, 'worldMatrix', 'ParamMatrix4');
o3d.ParamObject.setUpO3DParam_(o3d.Transform, 'localMatrix', 'ParamMatrix4');
o3d.ParamObject.setUpO3DParam_(o3d.Transform, 'cull', 'ParamBoolean');
o3d.ParamObject.setUpO3DParam_(o3d.Transform,
                               'boundingBox', 'ParamBoundingBox');


o3d.Transform.prototype.__defineSetter__('parent',
    function(p) {
      if (this.parent_ != null) {
        o3d.removeFromArray(this.parent_.children, this);
		this.parent_.recalculateBoundingBox();
      }
      this.parent_ = p;
      if (p) {
        p.addChild(this);
      }
    }
);

o3d.Transform.prototype.__defineGetter__('parent',
    function(p) {
      return this.parent_;
    }
);

o3d.Transform.prototype.__defineSetter__('localMatrix', 
	function(lm) {
      var param = this.getParam('localMatrix');
	  param.value = lm;
	  this._transformBoundingBox();
	}
);

o3d.Transform.prototype.__defineGetter__('localMatrix',
	function(lm) {
	    var param = this.getParam('localMatrix');
		return param.value;
	}
);

o3d.Transform.prototype.__defineSetter__('shapes', 
	function(s) {
      this.shapes_ = s;
	  this.recalculateBoundingBox();
	}
);

o3d.Transform.prototype.__defineGetter__('shapes', 
	function(s) {
      return this.shapes_;
	}
);

/**
 * Adds a child transform.
 * @param {o3d.Transform} The new child.
 */
o3d.Transform.prototype.addChild = function(child) {
  this.children.push(child);
  this.recalculateBoundingBox();
};


/**
 * Returns all the transforms under this transform including this one.
 *
 * Note that modifications to this array [e.g. additions to it] will not affect
 * the underlying Transform, while modifications to the members of the array
 * will affect them.
 *
 *  An array containing the transforms of the subtree.
 */
o3d.Transform.prototype.getTransformsInTree =
    function() {
  var result = [];
  o3d.Transform.getTransformInTreeRecursive_(this, result);
  return result;
};


/**
 * Recursive helper function for getTransformInTree.
 * @private
 */
o3d.Transform.getTransformInTreeRecursive_ =
    function(treeRoot, children) {
  children.push(treeRoot);
  var childrenArray = treeRoot.children;
  for (var ii = 0; ii < childrenArray.length; ++ii) {
    o3d.Transform.getTransformInTreeRecursive_(childrenArray[ii], children);
  }
};


/**
 * Searches for transforms that match the given name in the hierarchy under and
 * including this transform. Since there can be more than one transform with a
 * given name, results are returned in an array.
 *
 * Note that modifications to this array [e.g. additions to it] will not affect
 * the underlying Transform, while modifications to the members of the array
 * will affect them.
 *
 * @param {string} name Transform name to look for.
 * @return {Array.<o3d.Transform>}  An array containing the transforms of the
 *     under and including this transform matching the given name.
 */
o3d.Transform.prototype.getTransformsByNameInTree =
    function(name) {
  o3d.notImplemented();
};

/**
 * Evaluates and returns the current world matrix.
 *
 *  The updated world matrix.
 */
o3d.Transform.prototype.getUpdatedWorldMatrix =
    function() {
  var parentWorldMatrix;
  if (!this.parent) {
    parentWorldMatrix =
        [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];
  } else {
    parentWorldMatrix = this.parent.getUpdatedWorldMatrix();
  }
  o3d.Transform.compose(parentWorldMatrix, this.localMatrix, this.worldMatrix);
  return this.worldMatrix;
};

/**
 * Recalculates the bounding box of this transform and potentially its children.
 * 
 * @param {boolean} opt_forceTran optional flag indicating that child transforms
 *     should be forced to recalculate their bounding box even if they think it
 *     is valid (may be necessary)
 * @param {boolean} opt_forceShape optional flag indicating that shapes should
 *     be forced to recalculate the bounding boxes of their primitives even if
 *     they think they are valid (typically not necessary)
 */
o3d.Transform.prototype.recalculateBoundingBox =
    function(opt_forceTran, opt_forceShape) {
  var box = this.boundingBox,
      transforms = this.children,
	  shapes = this.shapes;
  
  box.valid = false;
  // Create the bounding box as the union of all the children bounding boxes and
  // all the shape bounding boxes.
  for (var i = 0; i < transforms.length; ++i) {
    var transform = transforms[i],
    	childBox = transform.boundingBox;
	
	if (opt_forceTran || !childBox.valid) {
		transform.recalculateBoundingBox(opt_forceTran, opt_forceShape);
		childBox = transform.boundingBox;
	}
    if (childBox.valid) {
      if (box.valid) {
        box = box.add(childBox);
      } else {
        box = childBox;
      }
    }
  }
  
  for (var i = 0; i < shapes.length; ++i) {
    var shape = shapes[i],
		shapeBox = shape.getBoundingBox(opt_forceShape);
    
    if (shapeBox.valid) {
      if (box.valid) {
        box = box.add(shapeBox);
      } else {
        box = shapeBox;
      }
    }
  }
  
  this.boundingBoxLocal = box;
  this._transformBoundingBox();
};

/**
 * 
 */
o3d.Transform.prototype._transformBoundingBox = function() {
  var newBox = this.boundingBoxLocal.mul(this.localMatrix);
  this.boundingBox = newBox;
  
  if (this.parent && newBox.valid) {
  	this.parent._trickleUp(this.boundingBox);
  }
};

/**
 * 
 * @param {Object} box
 */
o3d.Transform.prototype._trickleUp = function(box) {
  if (this.boundingBox.valid) {
  	this.boundingBoxLocal = this.boundingBoxLocal.add(box);
    this._transformBoundingBox();
  }
};

/**
 * Adds a shape do this transform.
 * @param {o3d.Shape} shape Shape to add.
 */
o3d.Transform.prototype.addShape =
    function(shape) {
  this.shapes.push(shape);
  this.recalculateBoundingBox();
};


/**
 * Removes a shape from this transform.
 * @param {o3d.Shape} shape Shape to remove.
 * @return {boolean}  true if successful, false if shape was not in
 *     this transform.
 */
o3d.Transform.prototype.removeShape =
    function(shape) {
  o3d.removeFromArray(this.shapes, shape);
  this.recalculateBoundingBox();
};


/**
 * Walks the tree of transforms starting with this transform and creates
 * draw elements. If an Element already has a DrawElement that uses material a
 * new DrawElement will not be created.
 * @param {o3d.Pack} pack Pack used to manage created elements.
 * @param {o3d.Material} material Material to use for each element. If you
 *     pass null, it will use the material on the element to which a draw
 *     element is being added. In other words, a DrawElement will use the
 *     material of the corresponding Element if material is null. This allows
 *     you to easily setup the default (just draw as is) by passing null or
 *     setup a shadow pass by passing in a shadow material.
 */
o3d.Transform.prototype.createDrawElements =
    function(pack, material) {
  var children = this.children;
  var shapes = this.shapes;

  for (var i = 0; i < shapes.length; ++i) {
    shapes[i].createDrawElements(pack, material);
  }

  for (var i = 0; i < children.length; ++i) {
    children[i].createDrawElements(pack, material);
  }
};


/**
 * Sets the local matrix of this transform to the identity matrix.
 */
o3d.Transform.prototype.identity = function() {
  var m = this.localMatrix;
  for (var i = 0; i < 4; ++i) {
    for (var j = 0; j < 4; ++j) {
      m[i][j] = i==j ? 1 : 0;
    }
  }
	  
  this._transformBoundingBox();
};


/*
 * Utility function to copose a matrix with another matrix.
 * Precomposes b with a, changing a, or if the target matrix if
 * one is provided.
 *
 * @param {!Array.<!Array.<number>>} a
 * @param {!Array.<!Array.<number>>} b
 * @param {!Array.<!Array.<number>>} opt_target
 */
o3d.Transform.compose = function(a, b, opt_target) {
  var t = opt_target || a;
  var a0 = a[0];
  var a1 = a[1];
  var a2 = a[2];
  var a3 = a[3];
  var b0 = b[0];
  var b1 = b[1];
  var b2 = b[2];
  var b3 = b[3];
  var a00 = a0[0];
  var a01 = a0[1];
  var a02 = a0[2];
  var a03 = a0[3];
  var a10 = a1[0];
  var a11 = a1[1];
  var a12 = a1[2];
  var a13 = a1[3];
  var a20 = a2[0];
  var a21 = a2[1];
  var a22 = a2[2];
  var a23 = a2[3];
  var a30 = a3[0];
  var a31 = a3[1];
  var a32 = a3[2];
  var a33 = a3[3];
  var b00 = b0[0];
  var b01 = b0[1];
  var b02 = b0[2];
  var b03 = b0[3];
  var b10 = b1[0];
  var b11 = b1[1];
  var b12 = b1[2];
  var b13 = b1[3];
  var b20 = b2[0];
  var b21 = b2[1];
  var b22 = b2[2];
  var b23 = b2[3];
  var b30 = b3[0];
  var b31 = b3[1];
  var b32 = b3[2];
  var b33 = b3[3];
  t[0].splice(0, 4, a00 * b00 + a10 * b01 + a20 * b02 + a30 * b03,
                    a01 * b00 + a11 * b01 + a21 * b02 + a31 * b03,
                    a02 * b00 + a12 * b01 + a22 * b02 + a32 * b03,
                    a03 * b00 + a13 * b01 + a23 * b02 + a33 * b03);
  t[1].splice(0, 4, a00 * b10 + a10 * b11 + a20 * b12 + a30 * b13,
                    a01 * b10 + a11 * b11 + a21 * b12 + a31 * b13,
                    a02 * b10 + a12 * b11 + a22 * b12 + a32 * b13,
                    a03 * b10 + a13 * b11 + a23 * b12 + a33 * b13);
  t[2].splice(0, 4, a00 * b20 + a10 * b21 + a20 * b22 + a30 * b23,
                    a01 * b20 + a11 * b21 + a21 * b22 + a31 * b23,
                    a02 * b20 + a12 * b21 + a22 * b22 + a32 * b23,
                    a03 * b20 + a13 * b21 + a23 * b22 + a33 * b23);
  t[3].splice(0, 4, a00 * b30 + a10 * b31 + a20 * b32 + a30 * b33,
                    a01 * b30 + a11 * b31 + a21 * b32 + a31 * b33,
                    a02 * b30 + a12 * b31 + a22 * b32 + a32 * b33,
                    a03 * b30 + a13 * b31 + a23 * b32 + a33 * b33);
};


/**
 * Tests whether two matrices are either equal in the sense that they
 * refer to the same memory, or equal in the sense that they have equal
 * entries.
 *
 * @param {!Array.<!Array.<number>>} a A matrix.
 * @param {!Array.<!Array.<number>>} b Another matrix.
 * @return {boolean} Whether they are equal.
 */
o3d.Transform.matricesEqual = function(a, b) {
  if (a==b) {
    return true;
  }
  for (var i = 0; i < 4; ++i) {
    for (var j = 0; j < 4; ++j) {
      if (a[i][j] != b[i][j]) {
        return false;
      }
    }
  }
  return true;
};


/**
 * Computes the transpose of the matrix a in place if no target is provided.
 * Or if a target is provided, turns the target into the transpose of a.
 *
 * @param {!Array.<!Array.<number>>} m A matrix.
 * @param {Array.<!Array.<number>>} opt_target
 *     The matrix to become the transpose of m.
 */
o3d.Transform.transpose = function(m, opt_target) {
  var t = opt_target || m;
  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];
  var m00 = m0[0];
  var m01 = m0[1];
  var m02 = m0[2];
  var m03 = m0[3];
  var m10 = m1[0];
  var m11 = m1[1];
  var m12 = m1[2];
  var m13 = m1[3];
  var m20 = m2[0];
  var m21 = m2[1];
  var m22 = m2[2];
  var m23 = m2[3];
  var m30 = m3[0];
  var m31 = m3[1];
  var m32 = m3[2];
  var m33 = m3[3];
  t[0].splice(0, 4, m00, m10, m20, m30);
  t[1].splice(0, 4, m01, m11, m21, m31);
  t[2].splice(0, 4, m02, m12, m22, m32);
  t[3].splice(0, 4, m03, m13, m23, m33);
};


/**
 * Computes the inverse of the matrix a in place if no target is provided.
 * Or if a target is provided, turns the target into the transpose of a.
 *
 * @param {!Array.<!Array.<number>>} m A matrix.
 * @param {Array.<!Array.<number>>} opt_target The matrix to become the
 *     inverse of a.
 */
o3d.Transform.inverse = function(m, opt_target) {
  var t = opt_target || m;
  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];
  var m00 = m0[0];
  var m01 = m0[1];
  var m02 = m0[2];
  var m03 = m0[3];
  var m10 = m1[0];
  var m11 = m1[1];
  var m12 = m1[2];
  var m13 = m1[3];
  var m20 = m2[0];
  var m21 = m2[1];
  var m22 = m2[2];
  var m23 = m2[3];
  var m30 = m3[0];
  var m31 = m3[1];
  var m32 = m3[2];
  var m33 = m3[3];

  var tmp_0 = m22 * m33;
  var tmp_1 = m32 * m23;
  var tmp_2 = m12 * m33;
  var tmp_3 = m32 * m13;
  var tmp_4 = m12 * m23;
  var tmp_5 = m22 * m13;
  var tmp_6 = m02 * m33;
  var tmp_7 = m32 * m03;
  var tmp_8 = m02 * m23;
  var tmp_9 = m22 * m03;
  var tmp_10 = m02 * m13;
  var tmp_11 = m12 * m03;
  var tmp_12 = m20 * m31;
  var tmp_13 = m30 * m21;
  var tmp_14 = m10 * m31;
  var tmp_15 = m30 * m11;
  var tmp_16 = m10 * m21;
  var tmp_17 = m20 * m11;
  var tmp_18 = m00 * m31;
  var tmp_19 = m30 * m01;
  var tmp_20 = m00 * m21;
  var tmp_21 = m20 * m01;
  var tmp_22 = m00 * m11;
  var tmp_23 = m10 * m01;

  var t0 = (tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31) -
      (tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31);
  var t1 = (tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31) -
      (tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31);
  var t2 = (tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31) -
      (tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31);
  var t3 = (tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21) -
      (tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21);

  var d = 1.0 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3);

  t[0].splice(0, 4, d * t0, d * t1, d * t2, d * t3);
  t[1].splice(0, 4, d * ((tmp_1 * m10 + tmp_2 * m20 + tmp_5 * m30) -
          (tmp_0 * m10 + tmp_3 * m20 + tmp_4 * m30)),
       d * ((tmp_0 * m00 + tmp_7 * m20 + tmp_8 * m30) -
          (tmp_1 * m00 + tmp_6 * m20 + tmp_9 * m30)),
       d * ((tmp_3 * m00 + tmp_6 * m10 + tmp_11 * m30) -
          (tmp_2 * m00 + tmp_7 * m10 + tmp_10 * m30)),
       d * ((tmp_4 * m00 + tmp_9 * m10 + tmp_10 * m20) -
          (tmp_5 * m00 + tmp_8 * m10 + tmp_11 * m20)));
  t[2].splice(0, 4, d * ((tmp_12 * m13 + tmp_15 * m23 + tmp_16 * m33) -
          (tmp_13 * m13 + tmp_14 * m23 + tmp_17 * m33)),
       d * ((tmp_13 * m03 + tmp_18 * m23 + tmp_21 * m33) -
          (tmp_12 * m03 + tmp_19 * m23 + tmp_20 * m33)),
       d * ((tmp_14 * m03 + tmp_19 * m13 + tmp_22 * m33) -
          (tmp_15 * m03 + tmp_18 * m13 + tmp_23 * m33)),
       d * ((tmp_17 * m03 + tmp_20 * m13 + tmp_23 * m23) -
          (tmp_16 * m03 + tmp_21 * m13 + tmp_22 * m23)));
  t[3].splice(0, 4, d * ((tmp_14 * m22 + tmp_17 * m32 + tmp_13 * m12) -
          (tmp_16 * m32 + tmp_12 * m12 + tmp_15 * m22)),
       d * ((tmp_20 * m32 + tmp_12 * m02 + tmp_19 * m22) -
          (tmp_18 * m22 + tmp_21 * m32 + tmp_13 * m02)),
       d * ((tmp_18 * m12 + tmp_23 * m32 + tmp_15 * m02) -
          (tmp_22 * m32 + tmp_14 * m02 + tmp_19 * m12)),
       d * ((tmp_22 * m22 + tmp_16 * m02 + tmp_21 * m12) -
          (tmp_20 * m12 + tmp_23 * m22 + tmp_17 * m02)));
};


/**
 * Pre-composes the local matrix of this Transform with a translation.  For
 * example, if the local matrix is a rotation then new local matrix will
 * translate by the given vector and then rotated.
 */
o3d.Transform.prototype.translate =
    function() {
  var v = arguments.length != 3 ? arguments[0] : arguments;
  var m = this.localMatrix;

  var v0 = v[0];
  var v1 = v[1];
  var v2 = v[2];
  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];
  var m00 = m0[0];
  var m01 = m0[1];
  var m02 = m0[2];
  var m03 = m0[3];
  var m10 = m1[0];
  var m11 = m1[1];
  var m12 = m1[2];
  var m13 = m1[3];
  var m20 = m2[0];
  var m21 = m2[1];
  var m22 = m2[2];
  var m23 = m2[3];
  var m30 = m3[0];
  var m31 = m3[1];
  var m32 = m3[2];
  var m33 = m3[3];

  m3.splice(0, 4, m00 * v0 + m10 * v1 + m20 * v2 + m30,
                  m01 * v0 + m11 * v1 + m21 * v2 + m31,
                  m02 * v0 + m12 * v1 + m22 * v2 + m32,
                  m03 * v0 + m13 * v1 + m23 * v2 + m33);
	  
  this._transformBoundingBox();
};


/**
 * Pre-composes the local matrix of this Transform with a rotation about the
 * x-axis.  For example, if the local matrix is a tranlsation, the new local
 * matrix will rotate around the x-axis and then translate.
 *
 * @param {number} radians The number of radians to rotate around x-axis.
 */
o3d.Transform.prototype.rotateX =
    function(angle) {
  var m = this.localMatrix;

  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];
  var m10 = m1[0];
  var m11 = m1[1];
  var m12 = m1[2];
  var m13 = m1[3];
  var m20 = m2[0];
  var m21 = m2[1];
  var m22 = m2[2];
  var m23 = m2[3];
  var c = Math.cos(angle);
  var s = Math.sin(angle);

  m1.splice(0, 4, c * m10 + s * m20,
                  c * m11 + s * m21,
                  c * m12 + s * m22,
                  c * m13 + s * m23);
  m2.splice(0, 4, c * m20 - s * m10,
                  c * m21 - s * m11,
                  c * m22 - s * m12,
                  c * m23 - s * m13);
	  
  this._transformBoundingBox();
};


/**
 * Takes a 4-by-4 matrix and a vector with 3 entries,
 * interprets the vector as a point, transforms that point by the matrix, and
 * returns the result as a vector with 3 entries.
 * @param {!o3djs.math.Matrix4} m The matrix.
 * @param {!o3djs.math.Vector3} v The point.
 * @return {!o3djs.math.Vector3} The transformed point.
 */
o3d.Transform.transformPoint = function(m, v) {
  var v0 = v[0];
  var v1 = v[1];
  var v2 = v[2];
  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];

  var d = v0 * m0[3] + v1 * m1[3] + v2 * m2[3] + m3[3];
  return [(v0 * m0[0] + v1 * m1[0] + v2 * m2[0] + m3[0]) / d,
          (v0 * m0[1] + v1 * m1[1] + v2 * m2[1] + m3[1]) / d,
          (v0 * m0[2] + v1 * m1[2] + v2 * m2[2] + m3[2]) / d];
};


/**
 * Takes a 4-by-4 matrix and a vector with 4 entries,
 * interprets the vector as a point, transforms that point by the matrix, and
 * returns the result as a vector with 4 entries.
 * @param {!o3djs.math.Matrix4} m The matrix.
 * @param {!o3djs.math.Vector4} v The vector.
 * @return {!o3djs.math.Vector4} The transformed vector.
 */
o3d.Transform.multiplyVector = function(m, v) {
  var v0 = v[0];
  var v1 = v[1];
  var v2 = v[2];
  var v3 = v[3];
  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];

  return [(v0 * m0[0] + v1 * m1[0] + v2 * m2[0] + v3 * m3[0]),
          (v0 * m0[1] + v1 * m1[1] + v2 * m2[1] + v3 * m3[1]),
          (v0 * m0[2] + v1 * m1[2] + v2 * m2[2] + v3 * m3[2]),
          (v0 * m0[3] + v1 * m1[3] + v2 * m2[3] + v3 * m3[3])];
};


/**
 * Takes a 4-by-4 matrix and a vector with 3 entries,
 * interprets the vector as a point, transforms that point by the matrix,
 * returning the z-component of the result only.
 * @param {!o3djs.math.Matrix4} m The matrix.
 * @param {!o3djs.math.Vector3} v The point.
 * @return {number} The z coordinate of the transformed point.
 */
o3d.Transform.transformPointZOnly = function(m, v) {
  var v0 = v[0];
  var v1 = v[1];
  var v2 = v[2];
  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];

  return (v0 * m0[2] + v1 * m1[2] + v2 * m2[2] + m3[2]) /
      (v0 * m0[3] + v1 * m1[3] + v2 * m2[3] + m3[3]);
};


/**
 * Pre-composes the local matrix of this Transform with a rotation about the
 * y-axis.  For example, if the local matrix is a translation, the new local
 * matrix will rotate around the y-axis and then translate.
 *
 * @param {number} radians The number of radians to rotate around y-axis.
 */
o3d.Transform.prototype.rotateY =
    function(angle) {
  var m = this.localMatrix;

  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];
  var m00 = m0[0];
  var m01 = m0[1];
  var m02 = m0[2];
  var m03 = m0[3];
  var m20 = m2[0];
  var m21 = m2[1];
  var m22 = m2[2];
  var m23 = m2[3];
  var c = Math.cos(angle);
  var s = Math.sin(angle);

  m0.splice(0, 4, c * m00 - s * m20,
                  c * m01 - s * m21,
                  c * m02 - s * m22,
                  c * m03 - s * m23);
  m2.splice(0, 4, c * m20 + s * m00,
                  c * m21 + s * m01,
                  c * m22 + s * m02,
                  c * m23 + s * m03);
	  
  this._transformBoundingBox();
};


/**
 * Pre-composes the local matrix of this Transform with a rotation about the
 * z-axis.  For example, if the local matrix is a translation, the new local
 * matrix will rotate around the z-axis and then translate.
 *
 * @param {number} radians The number of radians to rotate around z-axis.
 */
o3d.Transform.prototype.rotateZ =
    function(angle) {
  var m = this.localMatrix;

  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];
  var m00 = m0[0];
  var m01 = m0[1];
  var m02 = m0[2];
  var m03 = m0[3];
  var m10 = m1[0];
  var m11 = m1[1];
  var m12 = m1[2];
  var m13 = m1[3];
  var c = Math.cos(angle);
  var s = Math.sin(angle);

  m0.splice(0, 4, c * m00 + s * m10,
                  c * m01 + s * m11,
                  c * m02 + s * m12,
                  c * m03 + s * m13);
  m1.splice(0, 4, c * m10 - s * m00,
                  c * m11 - s * m01,
                  c * m12 - s * m02,
                  c * m13 - s * m03);
	  
  this._transformBoundingBox();
};


/**
 * Pre-composes the local matrix of this Transform with a rotation.
 * Interprets the three entries of the given vector as angles by which to
 * rotate around the x, y and z axes.  Rotates around the x-axis first,
 * then the y-axis, then the z-axis.
 *
 * @param {!o3d.math.Vector3} v A vector of angles (in radians) by which
 *     to rotate around the x, y and z axes.
 */
o3d.Transform.prototype.rotateZYX =
    function(v) {
  var m = this.localMatrix;

  var sinX = Math.sin(v[0]);
  var cosX = Math.cos(v[0]);
  var sinY = Math.sin(v[1]);
  var cosY = Math.cos(v[1]);
  var sinZ = Math.sin(v[2]);
  var cosZ = Math.cos(v[2]);

  var cosZSinY = cosZ * sinY;
  var sinZSinY = sinZ * sinY;

  var r00 = cosZ * cosY;
  var r01 = sinZ * cosY;
  var r02 = -sinY;
  var r10 = cosZSinY * sinX - sinZ * cosX;
  var r11 = sinZSinY * sinX + cosZ * cosX;
  var r12 = cosY * sinX;
  var r20 = cosZSinY * cosX + sinZ * sinX;
  var r21 = sinZSinY * cosX - cosZ * sinX;
  var r22 = cosY * cosX;

  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];

  var m00 = m0[0];
  var m01 = m0[1];
  var m02 = m0[2];
  var m03 = m0[3];
  var m10 = m1[0];
  var m11 = m1[1];
  var m12 = m1[2];
  var m13 = m1[3];
  var m20 = m2[0];
  var m21 = m2[1];
  var m22 = m2[2];
  var m23 = m2[3];
  var m30 = m3[0];
  var m31 = m3[1];
  var m32 = m3[2];
  var m33 = m3[3];

  m0.splice(0, 4,
      r00 * m00 + r01 * m10 + r02 * m20,
      r00 * m01 + r01 * m11 + r02 * m21,
      r00 * m02 + r01 * m12 + r02 * m22,
      r00 * m03 + r01 * m13 + r02 * m23);

  m1.splice(0, 4,
      r10 * m00 + r11 * m10 + r12 * m20,
      r10 * m01 + r11 * m11 + r12 * m21,
      r10 * m02 + r11 * m12 + r12 * m22,
      r10 * m03 + r11 * m13 + r12 * m23);

  m2.splice(0, 4,
      r20 * m00 + r21 * m10 + r22 * m20,
      r20 * m01 + r21 * m11 + r22 * m21,
      r20 * m02 + r21 * m12 + r22 * m22,
      r20 * m03 + r21 * m13 + r22 * m23);
	  
  this._transformBoundingBox();
};


/**
 * Pre-composes the local matrix of this Transform with a rotation around the
 * given axis.  For example, if the local matrix is a translation, the new
 * local matrix will rotate around the given axis and then translate.
 *
 * @param {number} angle The number of radians to rotate.
 * @param {!o3d.math.Vector3} axis a non-zero vector representing the axis
 *     around which to rotate.
 */
o3d.Transform.prototype.axisRotate =
    function(axis, angle) {
  o3d.Transform.axisRotateMatrix(this.localMatrix, axis, angle);
};

o3d.Transform.axisRotateMatrix =
    function(m, axis, angle, opt_target) {
  opt_target = opt_target || m;
  var x = axis[0];
  var y = axis[1];
  var z = axis[2];
  var n = Math.sqrt(x * x + y * y + z * z);
  x /= n;
  y /= n;
  z /= n;
  var xx = x * x;
  var yy = y * y;
  var zz = z * z;
  var c = Math.cos(angle);
  var s = Math.sin(angle);
  var oneMinusCosine = 1 - c;

  var r00 = xx + (1 - xx) * c;
  var r01 = x * y * oneMinusCosine + z * s;
  var r02 = x * z * oneMinusCosine - y * s;
  var r10 = x * y * oneMinusCosine - z * s;
  var r11 = yy + (1 - yy) * c;
  var r12 = y * z * oneMinusCosine + x * s;
  var r20 = x * z * oneMinusCosine + y * s;
  var r21 = y * z * oneMinusCosine - x * s;
  var r22 = zz + (1 - zz) * c;

  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];

  var m00 = m0[0];
  var m01 = m0[1];
  var m02 = m0[2];
  var m03 = m0[3];
  var m10 = m1[0];
  var m11 = m1[1];
  var m12 = m1[2];
  var m13 = m1[3];
  var m20 = m2[0];
  var m21 = m2[1];
  var m22 = m2[2];
  var m23 = m2[3];
  var m30 = m3[0];
  var m31 = m3[1];
  var m32 = m3[2];
  var m33 = m3[3];

  opt_target[0].splice(0, 4,
      r00 * m00 + r01 * m10 + r02 * m20,
      r00 * m01 + r01 * m11 + r02 * m21,
      r00 * m02 + r01 * m12 + r02 * m22,
      r00 * m03 + r01 * m13 + r02 * m23);

  opt_target[1].splice(0, 4,
      r10 * m00 + r11 * m10 + r12 * m20,
      r10 * m01 + r11 * m11 + r12 * m21,
      r10 * m02 + r11 * m12 + r12 * m22,
      r10 * m03 + r11 * m13 + r12 * m23);

  opt_target[2].splice(0, 4,
      r20 * m00 + r21 * m10 + r22 * m20,
      r20 * m01 + r21 * m11 + r22 * m21,
      r20 * m02 + r21 * m12 + r22 * m22,
      r20 * m03 + r21 * m13 + r22 * m23);

  opt_target[3].splice(0, 4, m30, m31, m32, m33);
};


/**
 * Pre-composes the local matrix of this Transform with a rotation defined by
 * the given quaternion.
 *
 * @param {o3d.math.Quat} q A non-zero quaternion to be interpreted as a
 *     rotation.
 */
o3d.Transform.prototype.quaternionRotate =
    function(q) {
   var m = this.localMatrix;

  var qX = q[0];
  var qY = q[1];
  var qZ = q[2];
  var qW = q[3];

  var qWqW = qW * qW;
  var qWqX = qW * qX;
  var qWqY = qW * qY;
  var qWqZ = qW * qZ;
  var qXqW = qX * qW;
  var qXqX = qX * qX;
  var qXqY = qX * qY;
  var qXqZ = qX * qZ;
  var qYqW = qY * qW;
  var qYqX = qY * qX;
  var qYqY = qY * qY;
  var qYqZ = qY * qZ;
  var qZqW = qZ * qW;
  var qZqX = qZ * qX;
  var qZqY = qZ * qY;
  var qZqZ = qZ * qZ;

  var d = qWqW + qXqX + qYqY + qZqZ;

  o3d.Transform.compose(this.localMatrix, [
    [(qWqW + qXqX - qYqY - qZqZ) / d,
     2 * (qWqZ + qXqY) / d,
     2 * (qXqZ - qWqY) / d, 0],
    [2 * (qXqY - qWqZ) / d,
     (qWqW - qXqX + qYqY - qZqZ) / d,
     2 * (qWqX + qYqZ) / d, 0],
    [2 * (qWqY + qXqZ) / d,
     2 * (qYqZ - qWqX) / d,
     (qWqW - qXqX - qYqY + qZqZ) / d, 0],
    [0, 0, 0, 1]]);
	  
  this._transformBoundingBox();
};


/**
 * Pre-composes the local matrix of this transform by a scaling transformation.
 * For example, if the local matrix is a rotation, the new local matrix will
 * scale and then rotate.
 */
o3d.Transform.prototype.scale =
    function() {
     var v;
  if (arguments.length == 3) {
    v = arguments;
  } else {
    v = arguments[0];
  }

  var m = this.localMatrix;

  var v0 = v[0];
  var v1 = v[1];
  var v2 = v[2];

  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];

  m0.splice(0, 4, v0 * m0[0], v0 * m0[1], v0 * m0[2], v0 * m0[3]);
  m1.splice(0, 4, v1 * m1[0], v1 * m1[1], v1 * m1[2], v1 * m1[3]);
  m2.splice(0, 4, v2 * m2[0], v2 * m2[1], v2 * m2[2], v2 * m2[3]);
  
  this._transformBoundingBox();
};


/**
 * Utility function to flatten an o3djs-style matrix
 * (which is an array of arrays) into one array of entries.
 * @param {Array.<Array.<number> >} m The o3djs-style matrix.
 * @return {Array.<number>} The flattened matrix.
 */
o3d.Transform.flattenMatrix4 = function(m) {
  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];
  return [m0[0], m0[1], m0[2], m0[3],
          m1[0], m1[1], m1[2], m1[3],
          m2[0], m2[1], m2[2], m2[3],
          m3[0], m3[1], m3[2], m3[3]];
};


/**
 * Traverses the transform tree starting at this node and adds DrawElements
 * for each shape to DrawList.
 * @param {Array.<Object>} drawListInfos A list of objects containing a draw
 *     list and matrix information.
 * @param {o3d.math.Matrix4} opt_parentWorldMatrix
 */
o3d.Transform.prototype.traverse =
    function(drawListInfos, opt_parentWorldMatrix) {
  this.gl.client.render_stats_['transformsProcessed']++;
  if (drawListInfos.length == 0 || !this.visible) {
    return;
  }
  opt_parentWorldMatrix =
      opt_parentWorldMatrix ||
          [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];

  o3d.Transform.compose(
      opt_parentWorldMatrix, this.localMatrix, this.worldMatrix);

  var remainingDrawListInfos = [];

  if (this.cull) {
    if (this.boundingBox) {
      for (var i = 0; i < drawListInfos.length; ++i) {
        var drawListInfo = drawListInfos[i];

        var worldViewProjection = [[], [], [], []];
        o3d.Transform.compose(drawListInfo.context.view,
            this.worldMatrix, worldViewProjection);
        o3d.Transform.compose(drawListInfo.context.projection,
            worldViewProjection, worldViewProjection);

        if (this.boundingBox.inFrustum(worldViewProjection)) {
          remainingDrawListInfos.push(drawListInfo);
        }
      }
    }
  } else {
    remainingDrawListInfos = drawListInfos;
  }

  if (remainingDrawListInfos.length == 0) {
    this.gl.client.render_stats_['transformsCulled']++;
    return;
  }

  var children = this.children;
  var shapes = this.shapes;

  for (var i = 0; i < shapes.length; ++i) {
    shapes[i].writeToDrawLists(remainingDrawListInfos, this.worldMatrix, this);
  }

  for (var i = 0; i < children.length; ++i) {
    children[i].traverse(remainingDrawListInfos, this.worldMatrix);
  }
};


/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * A Pack object functions as a container for O3D objects. The Pack
 * is used to control the lifetime scope of a collection of objects in bulk. The
 * Pack object achieves this by simply storing a set of references to its
 * contained objects, which ensures that the ref-counts for those objects never
 * reach zero while the pack is alive.
 * @constructor
 */
o3d.Pack = function() {
  o3d.NamedObject.call(this);
  this.objects_ = [];
};
o3d.inherit('Pack', 'NamedObject');


/**
 * Removes all internal references to the Pack from the client.
 * The pack, and all objects contained in it are permitted to be destroyed
 * after the pack's destruction.  Objects will only be destroyed after all
 * references to them have been removed.
 *
 * NOTE: Calling pack.destroy does NOT free your resources. It justs releases
 * the pack's reference to those resources.  An example should hopefully make
 * it clearer.
 *
 * pack.destroy() is effectively almost the same as this.
 *
 * var objectsInPack = pack.getObjectsByClassName('o3d.ObjectBase');
 * for (var ii = 0; ii < objectsInPack.length; ++ii) {
 *   pack.removeObject(objectsInPack[ii]);
 * }
 *
 * The only difference is that after all the objects are removed the pack
 * itself will be released from the client.  See documentation on
 * pack.removeObject for why this is important.
 *
 * It's important to note that many objects are only referenced by the pack.
 * Textures, Effects, Materials, for example. That means the moment you call
 * pack.destroy() those objects will be freed. If the client then tries to
 * render and some objects are missing you'll immediately get an error.
 */
o3d.Pack.prototype.destroy = function() {
  this.objects_ = [];
  this.client.destroyPack(this);
};



/**
 * Removes a pack's reference to an object. Any object created from
 * pack.create___ function can be removed. This releases the pack's reference
 * to that object so if nothing else is referencing that object it will be
 * deleted.
 *
 * NOTE: Calling pack.removeObject does NOT automatically free your resource.
 * It just releases the pack's reference to that resource. An example should
 * hopefully make it clearer.
 *
 * Suppose you make a transform like this:
 *
 *
 * var myTransform = pack.createObject('Transform');
 * myTransform.parent = g_client.root;
 * pack.removeObject(myTransform);
 *
 *
 * In the code above, myTransform is referenced twice. Once by the pack, and
 * again by g_client.root  So in this case, calling pack.removeObject()
 * only releases the pack's reference leaving the reference by g_client.root.
 *
 *
 * myTransform.parent = null;
 *
 *
 * Now the last reference has been removed and myTransform will be freed.
 *
 * @param {o3d.ObjectBase} object Object to remove.
 * @return {boolean}  True if the object was successfully removed.
 *     False if the object is not part of this pack.
 */
o3d.Pack.prototype.removeObject =
    function(object) {
  o3d.removeFromArray(this.objects_, object);
};


/**
 * Creates an Object by Class name.
 *
 * Note: You may omit the 'o3d.'.
 *
 * @param {string} type_name name of Class to create. Valid type names are:
 *      Bitmap
 *      Canvas
 *      CanvasLinearGradient
 *      CanvasPaint
 *      ClearBuffer
 *      Counter
 *      Curve
 *      DrawContext
 *      DrawElement
 *      DrawList
 *      DrawPass
 *      Effect
 *      FunctionEval
 *      IndexBuffer
 *      Material
 *      ParamArray
 *      ParamObject
 *      Primitive
 *      RenderFrameCounter
 *      RenderNode
 *      RenderSurfaceSet
 *      Sampler
 *      SecondCounter
 *      Shape
 *      Skin
 *      SkinEval
 *      SourceBuffer
 *      State
 *      StateSet
 *      StreamBank
 *      Texture2D
 *      TextureCUBE
 *      TickCounter
 *      Transform
 *      TreeTraversal
 *      VertexBuffer
 *      Viewport
 *      Matrix4AxisRotation
 *      Matrix4Composition
 *      Matrix4Scale
 *      Matrix4Translation
 *      ParamOp2FloatsToFloat2
 *      ParamOp3FloatsToFloat3
 *      ParamOp4FloatsToFloat4
 *      ParamOp16FloatsToMatrix4
 *      TRSToMatrix4
 * @return {o3d.ObjectBase}  The created object.
 */
o3d.Pack.prototype.createObject =
    function(type_name) {
  var foo = o3d.global.o3d[o3d.filterTypeName_(type_name)];
  if (typeof foo != 'function') {
    throw 'cannot find type in o3d namespace: ' + type_name
  }
  var object = new foo();
  object.gl = this.gl;
  object.clientId = o3d.Client.nextId++;
  this.objects_.push(object);
  return object;
};


/**
 * Creates a new Texture2D object of the specified size and format and
 * reserves the necessary resources for it.
 *
 * Note: If enable_render_surfaces is true, then the dimensions must be a
 * power of two.
 *
 * @param {number} width The width of the texture area in texels (max = 2048)
 * @param {number} height The height of the texture area in texels (max = 2048)
 * @param {o3d.Texture.Format} format The memory format of each texel
 * @param {number} levels The number of mipmap levels.  Use zero to create the
 *     compelete mipmap chain.
 * @param {boolean} enable_render_surfaces If true, the texture object will
 *     expose RenderSurface objects through GetRenderSurface(...).
 * @return {!o3d.Texture2D}  The Texture2D object.
 */
o3d.Pack.prototype.createTexture2D =
    function(width, height, format, levels, enable_render_surfaces) {
  var texture = this.createObject('Texture2D');
  texture.init_(width, height, format, levels, enable_render_surfaces);
  return texture;
};


/**
 * Creates a new TextureCUBE object of the specified size and format and
 * reserves the necessary resources for it.
 * Note:  If enable_render_surfaces is true, then the dimensions must be a
 * power of two.
 *
 * @param {number} edgeLength The edge of the texture area in texels
 *     (max = 2048)
 * @param {o3d.Texture.Format} format The memory format of each texel.
 * @param {number} levels The number of mipmap levels.   Use zero to create
 *     the compelete mipmap chain.
 * @param {boolean} enableRenderSurfaces If true, the texture object
 *     will expose RenderSurface objects through GetRenderSurface(...).
 * @return {!o3d.TextureCUBE}  The TextureCUBE object.
 */
o3d.Pack.prototype.createTextureCUBE =
    function(edgeLength, format, levels, enableRenderSurfaces) {
  var textureCube = this.createObject('TextureCUBE');
  textureCube.init_(edgeLength, format, levels, enableRenderSurfaces);
  return textureCube;
};


/**
 * Creates a new RenderDepthStencilSurface object of a format suitable for use
 * as a depth-stencil render target.
 * Note: The dimensions of the RenderDepthStencilSurface must be a power of
 *     two.
 *
 * @param {number} width The width of the RenderSurface in pixels
 * @param {number} height The height of the RenderSurface in pixels
 * @return {!o3d.RenderDepthStencilSurface}  The RenderSurface object.
 */
o3d.Pack.prototype.createDepthStencilSurface =
    function(width, height) {
  var surface = this.createObject("RenderDepthStencilSurface");
  surface.initWithSize_(width, height);
  return surface;
};


/**
 * Search the pack for all objects of a certain class with a certain name.
 *
 * Note that modifications to this array [e.g. push()] will not affect
 * the underlying Pack, while modifications to the array's members
 * will affect them.
 *
 * @param {string} name Name to look for
 * @param {string} class_type_name the Class of the object. It is okay
 *     to pass base types for example "o3d.RenderNode" will return
 *     ClearBuffers, DrawPasses, etc...
 * @return {!Array.<!o3d.ObjectBase>}  Array of Objects.
 */
o3d.Pack.prototype.getObjects =
    function(name, class_type_name) {
  class_type_name = o3d.filterTypeName_(class_type_name);

  var found = [];

  for (var i = 0; i < this.objects_.length; ++i) {
    var object = this.objects_[i];
    if (object.isAClassName(class_type_name) &&
        object.name == name) {
      found.push(object);
    }
  }

  return found;
};


/**
 * Search the pack for all objects of a certain class
 *
 * Note that modifications to this array [e.g. push()] will not affect
 * the underlying Pack, while modifications to the array's members
 * will affect them.
 *
 * @param {string} class_type_name the Class of the object. It is
 *     okay to pass base types for example "o3d.RenderNode" will return
 *     ClearBuffers, DrawPasses, etc...
 * @return {!Array.<!o3d.ObjectBase>}  Array of Objects.
 */
o3d.Pack.prototype.getObjectsByClassName =
    function(class_type_name) {
  class_type_name = o3d.filterTypeName_(class_type_name);

  var found = [];

  for (var i = 0; i < this.objects_.length; ++i) {
    var object = this.objects_[i];
    if (object.isAClassName(class_type_name)) {
      found.push(object);
    }
  }

  return found;
};


/**
 * All the objects managed by this pack.
 *
 * Each access to this field gets the entire list so it is best to get it
 * just once. For example:
 *
 * var objects = pack.objects;
 * for (var i = 0; i < objects.length; i++) {
 *   var object = objects[i];
 * }
 *
 *
 * Note that modifications to this array [e.g. push()] will not affect
 * the underlying Pack, while modifications to the array's members
 * will affect them.
 */
o3d.Pack.prototype.objects_ = [];


/**
 * Creates a FileRequest to be used to asynchronously load a Texture or
 * RawData. Note: Loading a "TEXTURE" is deprecated. The recommended way to
 * load a texture is to load a RawData, use that to create Bitmap, Massage
 * the Bitmap to your liking the use that to create a Texture.
 * @param {string} type Must be "TEXTURE" or "RAWDATA"
 * @return {!o3d.FileRequest}  a FileRequest
 */
o3d.Pack.prototype.createFileRequest =
    function(type) {
  return this.createObject('FileRequest');
};

/**
 * Creates an ArchiveRequest so we can stream in assets from an archive.
 * @return {!o3d.ArchiveRequest}  an ArchiveRequest
 */
o3d.Pack.prototype.createArchiveRequest =
    function() {
  return this.createObject('ArchiveRequest');
};

/**
 * Create Bitmaps from RawData.
 *
 * If you load a cube map you'll get an array of 6 Bitmaps.
 * If you load a volume map you'll get an array of n Bitmaps.
 * If there is an error you'll get an empty array.
 *
 * @param {!o3d.RawData} raw_data contains the bitmap data in a supported
 *     format.
 * @return {!Array.<!o3d.Bitmap>}  An Array of Bitmaps object.
 */
o3d.Pack.prototype.createBitmapsFromRawData =
    function(raw_data) {
  var bitmap = this.createObject('Bitmap')
  if (!raw_data.image_) {
    throw ('Cannot create bitmap from non-image data.');
    return [];
  }
  bitmap.height = raw_data.image_.height;
  bitmap.width = raw_data.image_.width;

  var canvas = document.createElement('CANVAS');

  canvas.width = bitmap.width;
  canvas.height = bitmap.height;
  var context = canvas.getContext('2d');
  context.drawImage(raw_data.image_,
      0, 0, bitmap.width, bitmap.height);

  bitmap.canvas_ = canvas;
  // Most images require a vertical flip.
  bitmap.flipVerticallyLazily_();

  // TODO(petersont): Find out if any other formats are possible at this point.
  bitmap.format = o3d.Texture.ARGB8;
  bitmap.numMipmaps = 1;

  return [bitmap];
};


/**
 * Create RawData given a data URL.
 * @param {string} data_url The data URL from which to create the RawData.
 * @return {!o3d.RawData}  The RawData.
 */
o3d.Pack.prototype.createRawDataFromDataURL =
    function(data_url) {
  o3d.notImplemented();
};

/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * Creates BoundingBox from minExtent and maxExtent
 * @param {!o3d.math.Point3} opt_minExtent optional minimum extent of the box.
 * @param {!o3d.math.Point3} opt_maxExtent optional maximum extent of the box.
 * @constructor
 */
o3d.BoundingBox =
    function(opt_minExtent, opt_maxExtent) {
  o3d.ParamObject.call(this);
  var minExtent = opt_minExtent || [0, 0, 0];
  var maxExtent = opt_maxExtent || [0, 0, 0];

  this.minExtent = [minExtent[0], minExtent[1], minExtent[2]];
  this.maxExtent = [maxExtent[0], maxExtent[1], maxExtent[2]];

  // If there were extents passed in, that validates the box.
  if (opt_minExtent && opt_maxExtent) {
    this.valid = true;
  }
};
o3d.inherit('BoundingBox', 'ParamObject');


/**
 * Computes a list of 8 3-dimensional vectors for the corners of the box.
 * @return {!Array.<Array<numbers>>} The list of corners.
 */
o3d.BoundingBox.prototype.corners_ = function() {
  var result = [];
  var m = [this.minExtent, this.maxExtent];
  for (var i = 0; i < 2; ++i) {
    for (var j = 0; j < 2; ++j) {
      for (var k = 0; k < 2; ++k) {
        result.push([m[i][0], m[j][1], m[k][2]]);
      }
    }
  }

  return result;
};


/**
 * Computes the smallest bounding box containing all the points in the given
 * list, and either modifies the optional box passed in to match, or returns
 * that box as a new box.
 * @param {!Array.<Array<numbers>>} points A non-empty list of points.
 * @param {o3d.BoundingBox} opt_targetBox Optional box to modify instead of
 *     returning a new box.
 * @private
 */
o3d.BoundingBox.fitBoxToPoints_ = function(points, opt_targetBox) {
  var target = opt_targetBox || new o3d.BoundingBox();
  for (var index = 0; index < 3; ++index) {
    target.maxExtent[index] = target.minExtent[index] = points[0][index];
    for (var i = 1; i < points.length; ++i) {
      var point = points[i];
      target.minExtent[index] = Math.min(target.minExtent[index], point[index]);
      target.maxExtent[index] = Math.max(target.maxExtent[index], point[index]);
    }
  }
  target.valid = true;
  return target;
};


/**
 * True if this boundingbox has been initialized.
 * @type {boolean}
 */
o3d.BoundingBox.prototype.valid = false;


/**
 * The min extent of the box.
 * @type {!o3d.math.Point3}
 */
o3d.BoundingBox.prototype.minExtent = [0, 0, 0];


/**
 * The max extent of the box.
 * @type {!o3d.math.Point3}
 */
o3d.BoundingBox.prototype.maxExtent = [0, 0, 0];


/**
 * Multiplies the bounding box by the given matrix returning a new bounding
 * box.
 * @param {!o3d.math.Matrix4} matrix The matrix to multiply by.
 * @return {!o3d.BoundingBox}  The new bounding box.
 */
o3d.BoundingBox.prototype.mul =
    function(matrix) {
  var corners = this.corners_();
  var new_corners = [];

  for (var i = 0; i < corners.length; ++i) {
    new_corners.push(o3d.Transform.transformPoint(matrix, corners[i]));
  }

  var newBox = o3d.BoundingBox.fitBoxToPoints_(new_corners);
  newBox.valid = this.valid;
  return newBox;
};


/**
 * Adds a bounding box to this bounding box returning a bounding box that
 * encompases both.
 * @param {!o3d.BoundingBox} box BoundingBox to add to this BoundingBox.
 * @return {!o3d.BoundingBox}  The new bounding box.
 */
o3d.BoundingBox.prototype.add =
    function(box) {
  var newBox = new o3d.BoundingBox(
    [Math.min(box.minExtent[0], this.minExtent[0]),
     Math.min(box.minExtent[1], this.minExtent[1]),
     Math.min(box.minExtent[2], this.minExtent[2])],
    [Math.max(box.maxExtent[0], this.maxExtent[0]),
     Math.max(box.maxExtent[1], this.maxExtent[1]),
     Math.max(box.maxExtent[2], this.maxExtent[2])]);
  
  newBox.valid = this.valid && box.valid;
  return newBox;
};


/**
 * Checks if a ray defined in same coordinate system as this box intersects
 * this bounding box.
 * @param {!o3d.math.Point3} start position of start of ray in local space.
 * @param {!o3d.math.Point3} end position of end of ray in local space.
 * @return {!o3d.RayIntersectionInfo}  RayIntersectionInfo. If result.value
 *     is false then something was wrong like using this function with an
 *     uninitialized bounding box. If result.intersected is true then the ray
 *     intersected the box and result.position is the exact point of
 *     intersection.
 */
o3d.BoundingBox.prototype.intersectRay =
    function(start, end) {
  // If there are six arguments, assume they are the coordinates of two points.
  if (arguments.length == 6) {
    start = [arguments[0], arguments[1], arguments[2]];
    end = [arguments[3], arguments[4], arguments[5]];
  }

  var result = new o3d.RayIntersectionInfo;

  if (this.valid) {
    result.valid = true;
    result.intersected = true;  // True until proven false.

    var kNumberOfDimensions = 3;
    var kRight = 0;
    var kLeft = 1;
    var kMiddle = 2;

    var direction = [end[0] - start[0], end[1] - start[1], end[2] - start[2]];
    var coord = [0, 0, 0];
    var inside = true;

    var quadrant = [];
    var max_t = [];
    var candidate_plane = [];

    for (var i = 0; i < kNumberOfDimensions; ++i) {
      quadrant.push(0.0);
      max_t.push(0.0);
      candidate_plane.push(0,0);
    }

    var which_plane;

    // Find candidate planes; this loop can be avoided if rays cast all from
    // the eye (assumes perpsective view).
    for (var i = 0; i < kNumberOfDimensions; ++i) {
      if (start[i] < this.minExtent[i]) {
        quadrant[i] = kLeft;
        candidate_plane[i] = this.minExtent[i];
        inside = false;
      } else if (start[i] >  this.maxExtent[i]) {
        quadrant[i] = kRight;
        candidate_plane[i] =  this.maxExtent[i];
        inside = false;
      } else  {
        quadrant[i] = kMiddle;
      }
    }

    // Ray origin inside bounding box.
    if (inside) {
      result.position = start;
      result.inside = true;
    } else {
      // Calculate T distances to candidate planes.
      for (var i = 0; i < kNumberOfDimensions; ++i) {
        if (quadrant[i] != kMiddle && direction[i] != 0.0) {
          max_t[i] = (candidate_plane[i] - start[i]) / direction[i];
        } else {
          max_t[i] = -1.0;
        }
      }

      // Get largest of the max_t's for final choice of intersection.
      which_plane = 0;
      for (var i = 1; i < kNumberOfDimensions; ++i) {
        if (max_t[which_plane] < max_t[i]) {
          which_plane = i;
        }
      }

      // Check final candidate actually inside box.
      if (max_t[which_plane] < 0.0) {
        result.intersected = false;
      } else {
        for (var i = 0; i < kNumberOfDimensions; ++i) {
          if (which_plane != i) {
            coord[i] = start[i] + max_t[which_plane] * direction[i];
            if (coord[i] < this.minExtent[i] || coord[i] > this.maxExtent[i]) {
              result.intersected = false;
              break;
            }
          } else {
            coord[i] = candidate_plane[i];
          }
        }

        // Ray hits box.
        result.position = coord;
      }
    }
  }

  return result;
};


/**
 * Returns true if the bounding box is inside the frustum matrix.
 * It checks all 8 corners of the bounding box against the 6 frustum planes
 * and determines whether there's at least one plane for which all 6 points lie
 * on the outside side of it.  In that case it reports that the bounding box
 * is outside the frustum.  Note that this is a conservative check in that
 * it in certain cases it will report that a box is in the frustum even if it
 * really isn't.  However if it reports that the box is outside then it's
 * guaranteed to be outside.
 * @param {!o3d.math.Matrix4} matrix Matrix to transform the box from its
 *     local space to view frustum space.
 * @return {boolean} True if the box is in the frustum.
 */
o3d.BoundingBox.prototype.inFrustum =
    function(matrix) {
  var corners = this.corners_();
  var bb_test = 0x3f;
  for (var i = 0; i < corners.length; ++i) {
    var corner = corners[i];
    var p = o3d.Transform.transformPoint(matrix, corner);
    bb_test &= (((p[0] > 1.0) << 0) |
                ((p[0] < -1.0) << 1) |
                ((p[1] > 1.0) << 2) |
                ((p[1] < -1.0) << 3) |
                ((p[2] > 1.0) << 4) |
                ((p[2] < 0.0) << 5));
    if (bb_test == 0) {
      return true;
    }
  }

  return (bb_test == 0);
};

/**
 * Calculate the coordinates of the center of the BoundingBox.
 * 
 * @return {number[3]} the XYZ coordinates of the center
 */
o3d.BoundingBox.prototype.getCenterOfGeometry = function() {
	var x = (this.minExtent[0] + this.maxExtent[0])/2,
		y = (this.minExtent[1] + this.maxExtent[1])/2,
		z = (this.minExtent[2] + this.maxExtent[2])/2;
	return [x, y, z];
};


/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * A DrawElement causes an Element to be Drawn with a particular material.
 * You can override other Effect parameters by adding corresponding params to
 * the DrawElement.
 *
 * @param {!o3d.Material} opt_material The material used to render this element.
 * @constructor
 */
o3d.DrawElement = function(opt_material) {
  o3d.ParamObject.call(this);

  /**
   * The Material for this DrawElement. If it is null the material of
   * owner will be used.
   * @type {o3d.Material}
   */
  this.material = opt_material || null;

  /**
   * The current owner of this Draw Element. Set to null to stop being owned.
   *
   * Note: DrawElements are referenced by the Pack they are created in
   * and their owner. If the DrawElement is removed from its Pack then
   * setting the owner to null will free the DrawElement. Or, visa
   * versa, if you set the DrawElement's owner to null then removing
   * it from its Pack will free the DrawElement.
   * @type {o3d.Element}
   */
  this.owner_ = null;
};
o3d.inherit('DrawElement', 'ParamObject');

o3d.ParamObject.setUpO3DParam_(o3d.DrawElement, 'material', 'ParamMaterial');

/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * An Element manages DrawElements for classes inherited from Element.
 *
 * @param {!o3d.Material} opt_material The Material used by this Element.
 * @param {!o3d.BoundingBox} opt_boundingBox The BoundingBox used by this
 *     Element for culling.
 * @param {!o3d.Point3} opt_zSortPoint The point to sort by when rendering
 *     this Element in a z ordered DrawPass.
 * @param {boolean} opt_cull Whether or not to attempt to cull this
 *     Element based on whether or not its bounding box is in the view
 *     frustum.
 * @constructor
 */
o3d.Element =
    function(opt_material, opt_boundingBox, opt_zSortPoint, opt_cull) {
  o3d.ParamObject.call(this);

  /**
   * The Material for this element.
   * @type {o3d.Material}
   */
  this.material = opt_material;

  /**
   * The BoundingBox for this element. If culling is on this bounding
   * box will be tested against the view frustum of any draw context
   * used to render this Element.
   * @type {o3d.BoundingBox}
   */
  this.boundingBox = opt_boundingBox || new o3d.BoundingBox();

  /**
   * The z sort point for this element. If this Element is drawn by a DrawPass
   * that is set to sort by z order this value will be multiplied by the
   * worldViewProjection matrix to compute a z value to sort by.
   * @type {o3d.Point3}
   */
  this.zSortPoint = opt_zSortPoint || [0, 0, 0];

  /**
   * The priority for this element. Used to sort if this Element is drawn by a
   * DrawPass that is set to sort by priority.
   * @type {number}
   */
  this.priority = 0;

  /**
   * The cull settings for this element. If true this Element will be
   * culled by the bounding box above compared to the view frustum it
   * is being rendered with.
   *
   * @type {boolean}
   */
  this.cull = opt_cull || false;

  /**
   * The current owner of this Draw Element. Pass in null to stop
   * being owned.
   *
   * Note: Elements are referenced by the Pack they are created in and
   * their owner. If the Element is removed from its Pack, then
   * setting the owner to null will free the Element. Or, visa versa,
   * if you set the Element's owner to null then removing it from its
   * Pack will free the Element.
   *
   * @type {o3d.Element}
   */
  this.owner_ = null;

  /**
   * Gets all the DrawElements under this Element.
   *
   * Each access to this field gets the entire list so it is best to get it
   * just once. For example:
   *
   * var drawElements = element.drawElements;
   * for (var i = 0; i < drawElements.length; i++) {
   *   var drawElement = drawElements[i];
   * }
   *
   *
   * Note that modifications to this array [e.g. push()] will not affect
   * the underlying Element, while modifications to the members of the array.
   * will affect them.
   *
   * @type {!Array.<!o3d.DrawElement>}
   */
  this.drawElements = [];
};
o3d.inherit('Element', 'ParamObject');

o3d.ParamObject.setUpO3DParam_(o3d.Element, 'material', 'ParamMaterial');
o3d.ParamObject.setUpO3DParam_(o3d.Element, 'boundingBox', 'ParamBoundingBox');
o3d.ParamObject.setUpO3DParam_(o3d.Element, 'zSortPoint', 'ParamFloat3');
o3d.ParamObject.setUpO3DParam_(o3d.Element, 'priority', 'ParamFloat');
o3d.ParamObject.setUpO3DParam_(o3d.Element, 'cull', 'ParamBoolean');

o3d.Element.prototype.__defineSetter__('owner',
    function(o) {
      this.owner_ = o;
      o.addElement(this);
    }
);

o3d.Element.prototype.__defineGetter__('owner',
    function() {
      return this.owner_;
    }
);

/**
 * Creates a DrawElement for this Element. Note that unlike
 * Shape.createDrawElements and Transform.createDrawElements this one will
 * create more than one element for the same material.
 *
 * @param {!o3d.Pack} pack pack used to manage created DrawElement.
 * @param {!o3d.Material} material material to use for DrawElement.
 *     Note: When a DrawElement with a material of null is rendered, the
 *     material on the corresponding Element will get used instead.
 *     This allows you to easily setup the default (just draw as is) by passing
 *     null or setup a shadow pass by passing in a shadow material.
 * @return {!o3d.DrawElement} The created draw element.
 */
o3d.Element.prototype.createDrawElement =
    function(pack, material) {
  drawElement = pack.createObject('DrawElement');
  drawElement.owner = this;
  drawElement.material = material;
  this.drawElements.push(drawElement);
  return drawElement;
};


/**
 * Computes the intersection of a ray in the same coordinate system as
 * the specified POSITION stream.
 * @param {number} position_stream_index Index of POSITION stream.
 * @param {o3d.Cull} cull which side of the triangles to ignore.
 * @param {!o3d.math.Point3} start position of start of ray in local space.
 * @param {!o3d.math.Point3} end position of end of ray. in local space.
 * @return {!o3d.RayIntersectionInfo}  RayIntersectionInfo class. If valid()
 *     is false then something was wrong, Check GetLastError(). If
 *     intersected() is true then the ray intersected a something. position()
 *     is the exact point of intersection.
 */
o3d.Element.prototype.intersectRay =
    function(position_stream_index, cull, start, end) {
  o3d.notImplemented();
};


/**
 * Computes the bounding box in same coordinate system as the specified
 * POSITION stream.
 * @param {number} position_stream_index Index of POSITION stream.
 * @return {!o3d.BoundingBox}  The boundingbox for this element in local space.
 */
o3d.Element.prototype.getBoundingBox =
    function(position_stream_index) {
  return this.boundingBox;
};


/**
 * Virtual function that renders the element.
 */
o3d.Element.prototype.render = function() { };


/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * A Field is a base class that manages a set of components in a
 * Buffer of a specific type. Fields are managed by Buffers and can not be
 * directly created. When a Buffer is destroyed or if a Field is removed from a
 * Buffer the Field's buffer property will be set to null.
 * @constructor
 */
o3d.Field = function() {
  o3d.NamedObject.call(this);
};
o3d.inherit('Field', 'NamedObject');

/**
 * The number of components in this field.
 * @type {number}
 */
o3d.Field.prototype.numComponents = 0;


/**
 * The Buffer the field belongs to.
 * @type {o3d.Buffer}
 */
o3d.Field.prototype.buffer = null;


/**
 * The offset of this field in the Buffer.
 * @type {number}
 */
o3d.Field.prototype.offset_ = 0;


/**
 * The size of one element of this field.
 * @type {number}
 */
o3d.Field.prototype.size = 0;


/**
 * Sets the values of the data stored in the field.
 *
 * The buffer for the field  must have already been created either through
 * buffer.set or through buffer.allocateElements.
 *
 * The number of values passed in must be a multiple of the number of
 * components needed for the field.
 *
 * @param {number} start_index index of first value to set.
 * @param {!Array.<number>} values Values to be stored in the buffer starting at
 *     index.
 */
o3d.Field.prototype.setAt =
    function(start_index, values) {
  this.buffer.lock();
  var l = values.length / this.numComponents;
  for (var i = 0; i < l; ++i) {
    for (var c = 0; c < this.numComponents; ++c) {
      this.buffer.array_[
          (start_index + i) * this.buffer.totalComponents + this.offset_ + c] =
              values[this.numComponents * i + c];
    }
  }
  this.buffer.unlock();
  return true;
};


/**
 * Gets the values stored in the field.
 *
 * @param {number} start_index index of the first value to get.
 * @param {number} num_elements number of elements to read from field.
 * @return {!Array.<number>}  The values of the field.
 */
o3d.Field.prototype.getAt =
    function(start_index, num_elements) {
  return this.buffer.getAtHelper_(start_index, num_elements, this.offset_,
      this.numComponents);
};



/**
 * A field that contains floating point numbers.
 * @constructor
 */
o3d.FloatField = function() {
  o3d.Field.call(this);
};
o3d.inherit('FloatField', 'Field');

/**
 * A field that contains unsigned integers.
 * @constructor
 */
o3d.UInt32Field = function() {
  o3d.Field.call(this);
};
o3d.inherit('UInt32Field', 'Field');

/**
 * A field that contains unsigned bytes.
 * @constructor
 */
o3d.UByteNField = function() {
  o3d.Field.call(this);
};
o3d.inherit('UByteNField', 'Field');


/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * The Buffer object is a low level container for a flat list of
 * floating point or integer values. These are currently used to define
 * geometry.
 * @constructor
 */
o3d.Buffer = function() {
  this.fields = [];
  this.array_ = null;
};
o3d.inherit('Buffer', 'NamedObject');


/**
 * The fields currently set on the buffer.
 * @type {!Array.<o3d.Field>}
 */
o3d.Buffer.prototype.fields = [];


/**
 * Total number of components.
 * @type {number}
 */
o3d.Buffer.prototype.totalComponents = 0;

/**
 * Index of the corresponding gl buffer object.
 * @type {number}
 */
o3d.Buffer.prototype.gl_buffer_ = 0;

/**
 * Function to create an array for the buffer.
 * @param {number} numElements
 * @return {!Float32Array}
 */
o3d.Buffer.prototype.createArray = function(numElements) {
  return new Float32Array(numElements);
};

o3d.Buffer.prototype.__defineGetter__('numElements',
    function() {
      return (!this.array_) ? 0 : this.array_.length / this.totalComponents;
    }
);

/**
 * Computes and stores the correct total components from the
 * fields so far.
 */
o3d.Buffer.prototype.updateTotalComponents_ = function() {
  var total = 0;
  for (var i = 0; i < this.fields.length; ++i) {
    this.fields[i].offset_ = total;
    total += this.fields[i].numComponents;
  }
  this.totalComponents = total;
};

/**
 * Allocates memory for the data to be stored in the buffer based on
 * the types of fields set on the buffer.
 *
 * @param {number} numElements Number of elements to allocate..
 * @return {boolean}  True if operation was successful.
 */
o3d.Buffer.prototype.allocateElements =
    function(numElements) {
  this.updateTotalComponents_();
  this.resize(numElements * this.totalComponents);
};

/**
 * Reallocate the array element to have the given number of elements.
 * @param {number} numElements The new number of elements.
 */
o3d.Buffer.prototype.resize = function(numElements) {
  this.gl_buffer_ = this.gl.createBuffer();
  // Callers (in particular the deserializer) occasionally call this
  // with floating-point numbers.
  this.array_ = this.createArray(Math.floor(numElements));
};

/**
 * Defines a field on this buffer.
 *
 * Note: Creating a field after having allocated the buffer is an expensive
 * operation as the data currently in the buffer has to be shuffled around
 * to make room for the new field.
 *
 * @param {string} field_type type of data in the field. Valid types
 *     are "FloatField", "UInt32Field", and "UByteNField".
 * @param {number} num_components number of components in the field.
 * @return {!o3d.Field}  The created field.
 */
o3d.Buffer.prototype.createField =
    function(fieldType, numComponents) {
  // Check if array has already been allocated. If so, we need to reshuffle
  // the data currently stored.
  var alreadyAllocated = this.array_ && this.array_.length > 0;
  var savedData = [];
  var numElements = this.numElements;

  // Make copies of the existing field data.
  if (alreadyAllocated) {
    for (var i = 0; i < this.fields.length; i++) {
      savedData[i] = this.fields[i].getAt(0, numElements);
    }
  }

  // Create the new field.
  var f = new o3d.Field();
  f.buffer = this;
  f.numComponents = numComponents;
  f.size = numComponents * (fieldType=='UByteNField' ? 1 : 4);
  this.fields.push(f);
  this.updateTotalComponents_();

  // Resize the buffer with the new field, and replace data.
  if (alreadyAllocated) {
    this.allocateElements(numElements);
    for (var i = 0; i < this.fields.length; i++) {
      var fieldData = savedData[i];
      if (fieldData) {
        this.fields[i].setAt(0, fieldData);
      }
    }
  }

  return f;
};


/**
 * Removes a field from this buffer.
 *
 * Note: Removing a field after having allocated the buffer is an expensive
 * operation as the data currently in the buffer has to be shuffled around
 * to remove the old field.
 *
 * @param {!o3d.Field} field field to remove.
 */
o3d.Buffer.prototype.removeField =
    function(field) {
  o3d.removeFromArray(this.fields, field);
  // TODO(petersont): Have this function actually shuffle the buffer around to
  // remove the field properly.
  this.updateTotalComponents_();
};


/**
 * Helper function for buffer's and field's getAt functions.  Gets elements in
 * the buffer as an array.
 * @param {number} start_index Index of the first element value to get.
 * @param {number} num_elements the number of elements to get.
 * @return {!Array.<number>}  An array of values.
 */
o3d.Buffer.prototype.getAtHelper_ =
    function(start_index, num_elements, offset, num_components) {
  var values = [];
  for (var i = 0; i < num_elements; ++i) {
    for (var c = 0; c < num_components; ++c) {
      values.push(this.array_[(start_index + i) *
          this.totalComponents + offset + c]);
    }
  }
  return values;
};


/**
 * Prepares the buffer for read/write.
 */
o3d.Buffer.prototype.lock = function() {
  // For now, this doesn't need to do anything.
};


/**
 * Delivers the buffer to the graphics hardware when read/write is finished.
 */
o3d.Buffer.prototype.unlock = function() {
  this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.gl_buffer_);
  this.gl.bufferData(this.gl.ARRAY_BUFFER, this.array_, this.gl.STATIC_DRAW);
};


/**
 * Sets the values in the buffer given array.
 *
 * @param {!Array.<number>} values contains data to assign to the Buffer
 *     data itself.
 * @return {boolean}  True if operation was successful.
 */
o3d.Buffer.prototype.set =
    function(values) {
  if (!values.length) {
    o3d.notImplemented();
  }
  if (this.array_ == null || this.array_.length != values.length) {
    this.resize(values.length);
  }
  this.lock();
  for (var i = 0; i < values.length; ++i) {
    this.array_[i] = values[i];
  }
  this.unlock();
};


/**
 * VertexBufferBase is a the base class for both VertexBuffer and SourceBuffer
 * @constructor
 */
o3d.VertexBufferBase = function() {
  o3d.Buffer.call(this);
};
o3d.inherit('VertexBufferBase', 'Buffer');


/**
 * Gets a copy of the values of the data stored in the buffer.
 * Modifying this copy has no effect on the buffer.
 */
o3d.VertexBufferBase.prototype.get = function() {
  return this.getAtHelper_(0, this.numElements,
      0, this.totalComponents);
};


/**
 * Gets a copy of a sub range of the values in the data stored in the buffer.
 * Modifying this copy has no effect on the buffer.
 *
 * @param {number} start_index index of the element value to get.
 * @param {number} num_elements the number of elements to get.
 * @return {!Array.<number>}  An array of values.
 */
o3d.VertexBufferBase.prototype.getAt =
    function(start_index, num_elements) {
  return this.getAtHelper_(start_index, num_elements, 0, this.totalComponents);
};


/**
 * VertexBuffer is a Buffer object used for storing vertex data for geometry.
 * (e.g. vertex positions, normals, colors, etc).
 * A VertexBuffer can be rendered directly by the GPU.
 * @constructor
 */
o3d.VertexBuffer = function() {
  o3d.Buffer.call(this);
};
o3d.inherit('VertexBuffer', 'Buffer');

/**
 * The name of the class as a string.
 * @type {string}
 */
o3d.VertexBuffer.prototype.className = "o3d.VertexBuffer";


/**
 * SourceBuffer is a Buffer object used for storing vertex data for
 * geometry. (e.g. vertex positions, normals, colors, etc).
 *
 * A SourceBuffer is the source for operations like skinning and morph
 * targets. It can not be directly rendered by the GPU.
 * @constructor
 */
o3d.SourceBuffer = function() {
  o3d.Buffer.call(this);
};
o3d.inherit('SourceBuffer', 'Buffer');


/**
 * IndexBuffer is a buffer object used for storing geometry index data (e.g.
 * triangle indices).
 * @constructor
 */
o3d.IndexBuffer = function() {
  o3d.Buffer.call(this);
};
o3d.inherit('IndexBuffer', 'Buffer');


/**
 * Type of the array element.
 * @type {!Uint16Array}
 */
o3d.IndexBuffer.prototype.createArray = function(numElements) {
  return new Uint16Array(numElements);
};

/**
 * Delivers the buffer to the graphics hardware when read/write is finished.
 */
o3d.IndexBuffer.prototype.unlock = function() {
  this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.gl_buffer_);
  this.gl.bufferData(
      this.gl.ELEMENT_ARRAY_BUFFER, this.array_, this.gl.STATIC_DRAW);
};
/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * Namespace o3d
 * @constructor
 */
o3d.Stream = function(semantic, semantic_index, field, start_index) {
  o3d.NamedObject.call(this);
  this.semantic = semantic;
  this.semanticIndex = semantic_index;
  this.field = field;
  this.startIndex = start_index;
};
o3d.inherit('Stream', 'NamedObject');

/**
 * @type {number}
 */
o3d.Stream.Semantic = goog.typedef;

/**
 *  Semantic,
 *  UNKNOWN_SEMANTIC = 0,
 *  POSITION,
 *  NORMAL,
 *  TANGENT,
 *  BINORMAL,
 *  COLOR,
 *  TEXCOORD,
 *  INFLUENCE_WEIGHTS,
 *  INFLUENCE_INDICES
 *
 * Semantics used when binding buffers to the streambank.  They determine how
 * the Stream links up to the shader inputs.
 */
o3d.Stream.UNKNOWN_SEMANTIC = 0;
o3d.Stream.POSITION = 1;
o3d.Stream.NORMAL = 2;
o3d.Stream.TANGENT = 3;
o3d.Stream.BINORMAL = 4;
o3d.Stream.COLOR = 5;
o3d.Stream.TEXCOORD = 6;
o3d.Stream.INFLUENCE_WEIGHTS = 7;
o3d.Stream.INFLUENCE_INDICES = 8;



/**
 * The associated Field.
 */
o3d.Stream.prototype.field = null;



/**
 * The semantic specified for the Stream.
 */
o3d.Stream.prototype.semantic = o3d.Stream.UNKNOWN_SEMANTIC;



/**
 * The semantic index specified for the Stream
 * (eg., TEXCOORD1 = 1, BINORMAL7 = 7, etc).
 */
o3d.Stream.prototype.semanticIndex = 0;



/**
 * The start index for the Stream.
 */
o3d.Stream.prototype.startIndex = 0;


/**
 * Gets the max number of vertices in this stream.
 *
 * @return {number} The maximum vertices available given the stream's settings
 *     and its buffer.
 * @private
 */
o3d.Stream.prototype.getMaxVertices_ = function() {
  var buffer = this.field.buffer;
  if (!buffer)
    return 0;

  var num_elements = buffer.numElements;
  if (this.startIndex > num_elements)
    return 0;

  return num_elements - this.startIndex;
};

/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * A VertexSource is an object that allows binding Streams such that the
 * VertexSource updates the Buffers of the Streams that have been bound to it.
 * An example of a VertexSource object is a SkinEval
 *
 * @constructor
 * @extends ParamObject
 */
o3d.VertexSource = function() {
  o3d.ParamObject.call(this);
};
o3d.inherit('VertexSource', 'ParamObject');

/**
 * Bind the source stream to the corresponding stream in this VertexSource.
 *
 * @param {o3d.VertexSource} source Source to get vertices from.
 * @param {o3d.Stream.Semantic} semantic The semantic of the vertices to get.
 * @param {number} semantic_index The semantic index of the vertices to get.
 * @return {boolean} True if success. False if failure. If the requested
 *     semantic or semantic index do not exist on the source or this source
 *     the bind will fail.
 */
o3d.VertexSource.prototype.bindStream = function(
    source, semantic, semantic_index) {
  if (source) {
    var source_param = source.getVertexStreamParam(semantic, semantic_index);
    var dest_param = this.getVertexStreamParam(semantic, semantic_index);
    if (source_param && dest_param &&
        source_param.stream.field.className ==
            dest_param.stream.field.className &&
        source_param.stream.field.numComponents ==
        dest_param.stream.field.numComponents) {
      dest_param.bind(source_param);
      source.streamWasBound_(this, semantic, semantic_index);
      return true;
    }
  }

  return false;
};

/**
 * Unbinds the requested stream.
 *
 * @param {o3d.Stream.Semantic} semantic The semantic of the vertices to unbind.
 * @param {number} semantic_index The semantic index of the vertices to unbind.
 * @return {boolean} True if unbound. False those vertices do not exist or were
 *     not bound.
 */
o3d.VertexSource.prototype.unbindStream = function(semantic, semantic_index) {
  var dest_param = this.getVertexStreamParam(semantic, semantic_index);
  if (dest_param && dest_param.inputConnection != null) {
    dest_param.unbindInput();
    return true;
  }
  return false;
};

/**
 * Used by bindStream. Each derived class must provide this function.
 *
 * @param {o3d.Stream.Semantic} semantic The semantic of the vertices to get.
 * @param {number} semantic_index The semantic index of the vertices to get.
 * @return {ParamVertexBufferStream} Returns the ParamVertexBufferStream that
 *     manages the given stream as an output param for this VertexSource.
 * @protected
 */
o3d.VertexSource.prototype.getVertexStreamParam = function(
    semantic, semantic_index) {
  o3d.notImplemented();
};

/**
 * Used by bindStream. Derived classes may override if needed.
 *
 * @param {o3d.VertexSource} dest VertexSource that bound to this VertexSource.
 * @param {o3d.ParamVertexBufferStream} dest_param Other param which was bound.
 * @protected
 */
o3d.VertexSource.prototype.streamWasBound_ = function(
    dest, semantic, semantic_index) {
};

/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * The StreamBank a collection of streams that hold vertices.
 * @constructor
 * @extends {o3d.VertexSource}
 */
o3d.StreamBank = function() {
  o3d.VertexSource.call(this);
  this.vertex_streams_ = [];
};
o3d.inherit('StreamBank', 'VertexSource');

/**
 * Array of streams.
 */
o3d.StreamBank.prototype.vertex_streams_ = [];

o3d.StreamBank.prototype.__defineGetter__('vertexStreams',
    function() {
      var result = [];
      for (var i = 0; i < this.vertex_streams_.length; ++i) {
        var stream_array = this.vertex_streams_[i];
        if (stream_array && stream_array.length) {
          for (var j = 0; j < stream_array.length; ++j) {
            var stream = stream_array[j];
            if (stream) {
              result.push(stream.stream);
            }
          }
        }
      }
      return result;
    }
);


/**
 * Binds a VertexBuffer field to the StreamBank and defines how the data in
 * the buffer should be interpreted. The field's buffer must be of a
 * compatible type otherwise the binding fails and the function returns false.
 * @param {o3d.Stream.Semantic} semantic The particular use of this stream.
 * @param {number} semantic_index Which index of a particular semantic to use.
 * @param {o3d.Field} field The field containing information for this stream.
 * @param {number} start_index The first element to use.
 * @return {boolean}  True if successful.
 */
o3d.StreamBank.prototype.setVertexStream =
    function(semantic, semantic_index, field, start_index) {
  if (this.vertex_streams_[semantic] == undefined) {
    this.vertex_streams_[semantic] = [];
  }
  var stream = new o3d.Stream(semantic, semantic_index, field, start_index);
  var stream_param = new o3d.ParamVertexBufferStream;
  stream_param.stream = stream;
  stream_param.owner_ = this;
  this.vertex_streams_[semantic][semantic_index] = stream_param;
};


/**
 * Searches the vertex streams bound to the StreamBank for one with the given
 * stream semantic.  If a stream is not found then it returns null.
 * @param {o3d.Stream.Semantic} semantic The particular use of this stream.
 * @param {number} semantic_index Which index of a particular semantic to use.
 * @return {o3d.Stream}  The found stream or null if it does not exist.
 */
o3d.StreamBank.prototype.getVertexStream =
    function(semantic, semantic_index) {
  if (this.vertex_streams_[semantic] == undefined) {
    return null;
  }
  if (!this.vertex_streams_[semantic][semantic_index]) {
    return null;
  }
  return this.vertex_streams_[semantic][semantic_index].stream;
};


/**
 * Searches the vertex streams bound to the StreamBank for one with the given
 * stream semantic.  If a stream is not found then it returns null.
 * @param {o3d.Stream.Semantic} semantic The particular use of this stream.
 * @param {number} semantic_index Which index of a particular semantic to use.
 * @return {o3d.ParamVertexBufferStream}  The found stream param or null if it
 *     does not exist.
 * @override
 * @protected
 */
o3d.StreamBank.prototype.getVertexStreamParam =
    function(semantic, semantic_index) {
  if (this.vertex_streams_[semantic] == undefined) {
    return null;
  }
  return this.vertex_streams_[semantic][semantic_index];
};


/**
 * Removes a vertex stream from this StreamBank.
 * @param {o3d.Stream.Semantic} semantic The particular use of this stream.
 * @param {o3d.Stream.Semantic} semantic_index Which index of a particular
 *     semantic to use.
 * @return {boolean}  true if the specified stream existed.
 */
o3d.StreamBank.prototype.removeVertexStream =
    function(semantic, semantic_index) {
  if (this.vertex_streams_[semantic] == undefined) {
    return false;
  }
  delete this.vertex_streams_[semantic][semantic_index];
  return true;
};


/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * A Primitive is a type of Element that is made from a list of points,
 * lines or triangles that use a single material.
 *
 * @param opt_streamBank o3d.StreamBank The StreamBank used by this
 *     Primitive.
 * @constructor
 */
o3d.Primitive = function(opt_streamBank) {
  o3d.Element.call(this);

  /**
   * The index buffer for the primitive. If null the primitive is non-indexed.
   * @type {o3d.IndexBuffer}
   */
  this.indexBuffer = null;

  /**
   * The stream bank this primitive uses for vertices.
   * @type {o3d.StreamBank}
   */
  this.streamBank = opt_streamBank || null;

  /**
   * The type of primitive the primitive is (i.e., POINTLIST, LINELIST,
   * TRIANGLELIST, etc.)
   *
   * @type {o3d.Primitive.Type}
   */
  this.primitiveType = o3d.Primitive.TRIANGLELIST;

  /**
   * The number of vertices the primitive has.
   *
   * @type {number}
   */
  this.numberVertices = 0;

  /**
   * The number of rendering primitives (i.e., triangles, points, lines) the
   * primitive has.
   *
   * @type {number}
   */
  this.numberPrimitives = 0;

  /**
   * The index of the first vertex to render.
   *
   * @type {number}
   */
  this.startIndex = 0;

  /**
   * The index buffer for the wireframe version of the primitive.
   * @type {o3d.IndexBuffer}
   * @private
   */
  this.wireframeIndexBuffer_ = null;
};
o3d.inherit('Primitive', 'Element');


/**
 * @type {number}
 */
o3d.Primitive.Type = goog.typedef;

/**
 * Type of geometric primitives used by the Primitive.
 */
o3d.Primitive.POINTLIST = 1;
o3d.Primitive.LINELIST = 2;
o3d.Primitive.LINESTRIP = 3;
o3d.Primitive.TRIANGLELIST = 4;
o3d.Primitive.TRIANGLESTRIP = 5;
o3d.Primitive.TRIANGLEFAN = 6;

o3d.ParamObject.setUpO3DParam_(o3d.Primitive, 'streamBank', 'ParamStreamBank');

/**
 * Binds the vertex and index streams required to draw the shape.
 */
o3d.Primitive.prototype.render = function() {
  var streamBank = this.streamBank;
  var indexBuffer = this.indexBuffer;

  var enabled_attribs = [];

  for (var semantic = 0;
       semantic < streamBank.vertex_streams_.length;
       ++semantic) {
    var streams = streamBank.vertex_streams_[semantic];
    if (streams && streams.length) {
      for (var semantic_index = 0;
           semantic_index < streams.length;
           ++semantic_index) {
        var gl_index = o3d.Effect.reverseSemanticMap_[semantic][semantic_index];
        var stream = streams[semantic_index].stream;
        var field = stream.field;
        var buffer = field.buffer;

        if (gl_index == undefined) {
          this.gl.client.error_callback('uknown semantic');
        }

        var stream_param = streams[semantic_index];
        while (!stream_param.owner_.updateStreams &&
               stream_param.inputConnection) {
          stream_param = stream_param.inputConnection;
        }
        if (stream_param.owner_.updateStreams) {
          // By now, stream_param should point to the SkinEval's streams.
          stream_param.owner_.updateStreams();  // Triggers updating.
        }

        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer.gl_buffer_);
        this.gl.enableVertexAttribArray(gl_index);
        enabled_attribs.push(gl_index);

        var kFloatSize = Float32Array.BYTES_PER_ELEMENT;

        this.gl.vertexAttribPointer(
            gl_index, field.numComponents, this.gl.FLOAT, false,
            buffer.totalComponents * kFloatSize, field.offset_ * kFloatSize);
      }
    }
  }

  this.gl.client.render_stats_['primitivesRendered'] += this.numberPrimitives;

  var glMode;
  var glNumElements;

  switch (this.primitiveType) {
    case o3d.Primitive.POINTLIST:
      glMode = this.gl.POINTS;
      glNumElements = this.numberPrimitives;
      break;
    case o3d.Primitive.LINELIST:
      glMode = this.gl.LINES;
      glNumElements = this.numberPrimitives * 2;
      break;
    case o3d.Primitive.LINESTRIP:
      glMode = this.gl.LINE_STRIP;
      glNumElements = this.numberPrimitives + 1;
      break;
    case o3d.Primitive.TRIANGLELIST:
      glMode = this.gl.TRIANGLES;
      glNumElements = this.numberPrimitives * 3;
      break;
    case o3d.Primitive.TRIANGLESTRIP:
      glMode = this.gl.TRIANGLE_STRIP;
      glNumElements = this.numberPrimitives + 2;
      break;
    case o3d.Primitive.TRIANGLEFAN:
      glMode = this.gl.TRIANGLE_FAN;
      glNumElements = this.numberPrimitives + 2;
      break;
    case o3d.Primitive.TRIANGLELIST:
    default:
      glMode = this.gl.TRIANGLES;
      glNumElements = this.numberPrimitives * 3;
      break;
  }

  var use_wireframe_indices = false;

  if (this.gl.fillMode_ == o3d.State.POINT) {
    // If the fill mode is points, then we just replace the gl primitive type
    // with POINTS and let the (possibly redundant) list of points draw.
    glMode = this.gl.POINTS;
  } else if (this.gl.fillMode_ == o3d.State.WIREFRAME) {
    // If the fill mode is lines, and the primitive type is some kind of
    // triangle, then we need to reorder indices to draw the right thing.

    if (this.primitiveType == o3d.Primitive.TRIANGLELIST ||
        this.primitiveType == o3d.Primitive.TRIANGLEFAN ||
        this.primitiveType == o3d.Primitive.TRIANGLESTRIP) {
      use_wireframe_indices = true;
      glMode = this.gl.LINES;
      this.computeWireframeIndices_();
    }
  }

  if (use_wireframe_indices) {
    indexBuffer = this.wireframeIndexBuffer_;

    switch(this.primitiveType) {
      default:
      case o3d.Primitive.TRIANGLELIST:
        glNumElements = this.numberPrimitives * 6;
        break;
      case o3d.Primitive.TRIANGLESTRIP:
      case o3d.Primitive.TRIANGLEFAN:
        glNumElements = (this.numberPrimitives == 0) ? 0 :
            this.numberPrimitives * 4 + 2;
        break;
    }
  }

  if (!indexBuffer) {
    this.gl.drawArrays(glMode, 0, glNumElements);
  } else {
    this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, indexBuffer.gl_buffer_);
    this.gl.drawElements(glMode,
                         glNumElements,
                         this.gl.UNSIGNED_SHORT,
                         0);
  }

  for (var i = 0; i < enabled_attribs.length; ++i) {
    this.gl.disableVertexAttribArray(enabled_attribs[i]);
  }
};


/**
 * Given n, gets index number n in the index buffer if there is an index buffer
 * otherwise returns n.
 * @param {number} n The number of the entry in the index buffer.
 * @return {return} The index.
 * @private
 */
o3d.Primitive.prototype.getIndex_ = function(n) {
  if (this.indexBuffer) {
    return this.indexBuffer.array_[n]
  }
  return n;
};


/**
 * Generates an index buffer for a wireframe outline of a triangle-based
 * primitive.
 * @private
 */
o3d.Primitive.prototype.computeWireframeIndices_ = function() {
  this.wireframeIndexBuffer_ = new o3d.IndexBuffer;
  this.wireframeIndexBuffer_.gl = this.gl;

  var numTriangles = this.numberPrimitives;
  var numLines = (this.primitiveType == o3d.Primitive.TRIANGLELIST) ?
    (3 * numTriangles) : (2 * numTriangles + 1);

  this.wireframeIndexBuffer_.resize(2 * numLines);

  var j = 0;  // The current index in wireframeIndices.
  switch (this.primitiveType) {
    default:
    case o3d.Primitive.TRIANGLELIST: {
      var wireframeIndices = this.wireframeIndexBuffer_.array_;
      this.wireframeIndexBuffer_.lock();
      // Iterate through triangles.
      for (var i = 0; i < numTriangles; ++i) {
        // Indices the vertices of the triangle a, b, c.
        var a = this.getIndex_(3 * i);
        var b = this.getIndex_(3 * i + 1);
        var c = this.getIndex_(3 * i + 2);
        wireframeIndices[j++] = a;
        wireframeIndices[j++] = b;
        wireframeIndices[j++] = b;
        wireframeIndices[j++] = c;
        wireframeIndices[j++] = c;
        wireframeIndices[j++] = a;
      }
      this.wireframeIndexBuffer_.unlock();
    }
    break;

    case o3d.Primitive.TRIANGLEFAN: {
      var wireframeIndices = this.wireframeIndexBuffer_.array_;
      this.wireframeIndexBuffer_.lock();
      // The first two points make a line.
      var z;
      if (numTriangles > 0) {
        z = this.getIndex_(0);
        wireframeIndices[j++] = z;
        wireframeIndices[j++] = this.getIndex_(1);
      }
      // Each additional point forms a new triangle by adding two lines.
      for (var i = 2; i < numTriangles + 2; ++i) {
        var a = this.getIndex_(i);
        wireframeIndices[j++] = z;
        wireframeIndices[j++] = a;
        wireframeIndices[j++] = a;
        wireframeIndices[j++] = this.getIndex_(i - 1);
      }
      this.wireframeIndexBuffer_.unlock();
    }
    break;

    case o3d.Primitive.TRIANGLESTRIP: {
      var wireframeIndices = this.wireframeIndexBuffer_.array_;
      this.wireframeIndexBuffer_.lock();
      // The frist two points make a line.
      var a;
      var b;
      if (numTriangles > 0) {
        a = this.getIndex_(0);
        b = this.getIndex_(1);
        wireframeIndices[j++] = a;
        wireframeIndices[j++] = b;
      }
      // Each additional point forms a new triangle by adding two lines.
      for (var i = 2; i < numTriangles + 2; ++i) {
        var c = this.getIndex_(i);
        wireframeIndices[j++] = b;
        wireframeIndices[j++] = c;
        wireframeIndices[j++] = c;
        wireframeIndices[j++] = a;
        a = b;
        b = c;
      }
      this.wireframeIndexBuffer_.unlock();
    }
    break;
  }
};

/**
 * Returns the three indices of the n-th triangle of this primitive. If the
 * primitive has no index buffer, then the buffer is assumed to be [0 ... n-1].
 * These indices can then be used to reference the vertex buffer and get the
 * triangle vertices' positions.
 *
 * @param {number} n The number of the triangle we want. Zero-indexed.
 * @return {!Array.<Number>} Array containing three indices that correspond to
 *    the n-th triangle of this primitive.
 * @private
 */
o3d.Primitive.prototype.computeTriangleIndices_ = function(n) {
  var indices;
  switch (this.primitiveType) {
    case o3d.Primitive.TRIANGLESTRIP:
      if (n % 2 == 0) {
        indices = [n, n + 1, n + 2];
      } else {
        indices = [n + 1, n, n + 2];
      }
      break;
    case o3d.Primitive.TRIANGLEFAN:
      indices = [0, n + 1, n + 2];
      break;
    case o3d.Primitive.TRIANGLELIST:
    default:
      indices = [3 * n, 3 * n + 1, 3 * n + 2];
      break;
  }
  if (this.indexBuffer) {
    var buffer = this.indexBuffer.array_;
    return [buffer[indices[0]],
            buffer[indices[1]],
            buffer[indices[2]]];
  } else {
    return indices;
  }
};

/**
 * Computes the intersection of a ray in the coordinate system of
 * the specified POSITION stream.
 * @param {number} position_stream_index Index of POSITION stream.
 * @param {o3d.Cull} cull which side of the triangles to ignore.
 * @param {!o3d.math.Point3} start position of start of ray in local space.
 * @param {!o3d.math.Point3} end position of end of ray. in local space.
 * @return {!o3d.RayIntersectionInfo}  RayIntersectionInfo class. If valid()
 *     is false then something was wrong, Check GetLastError(). If
 *     intersected() is true then the ray intersected a something. position()
 *     is the exact point of intersection.
 */
o3d.Primitive.prototype.intersectRay =
    function(position_stream_index, cull, start, end) {
  var result = new o3d.RayIntersectionInfo;
  result.valid = true;

  var streamBank = this.streamBank;
  var indexBuffer = this.indexBuffer;
  var positionStreams = this.streamBank.vertex_streams_[o3d.Stream.POSITION];
  var stream = positionStreams[position_stream_index].stream;

  var field = stream.field;
  var buffer = field.buffer;
  var numPoints = buffer.array_.length / buffer.totalComponents;
  var elements = field.getAt(0, numPoints);

  // The direction of the vector of the ray.
  var x = end[0] - start[0];
  var y = end[1] - start[1];
  var z = end[2] - start[2];

  // Find two vectors orthogonal to direction for use in quickly eliminating
  // triangles which can't possibly intersect the ray.
  var direction = [x, y, z];

  // Pick a vector orthogonal to direction called u.
  var ux = -y;
  var uy = x;
  var uz = 0;
  if (x * x + y * y < z * z) {
    ux = -z;
    uy = 0;
    uz = x;
  }

  // Cross product direction and u get a third orthogonal vector v.
  var vx = y * uz - z * uy;
  var vy = z * ux - x * uz;
  var vz = x * uy - y * ux;

  var udotstart = ux * start[0] + uy * start[1] + uz * start[2];
  var vdotstart = vx * start[0] + vy * start[1] + vz * start[2];

  // As we search for an intersection point, we keep track of how far out
  // from the start the point with this variable.
  var min_distance = 0;

  // Iterate through the indices and examine triples of indices that each
  // define a triangle.  For each triangle, we test for intersection with
  // the ray.  We need to find the closest one to start, so we have to
  // check them all.

  var numIndices = indexBuffer ? indexBuffer.array_.length : numPoints;
  switch (this.primitiveType) {
    case o3d.Primitive.TRIANGLESTRIP:
      numTriangles = numIndices - 2;
      break;
    case o3d.Primitive.TRIANGLEFAN:
      numTriangles = numIndices - 2;
      break;
    case o3d.Primitive.TRIANGLELIST:
    default:
      numTriangles = numIndices / 3;
      break;
  }

  for (var i = 0; i < numTriangles; ++i) {
    var indices = this.computeTriangleIndices_(i);

    // Check if the current triangle is too far to one side of the ray
    // to intersect at all.  (This is what the orthogonal vectors are for)
    var u_sides = [false, false, false];
    var v_sides = [false, false, false];
    for (var j = 0; j < 3; ++j) {
      var t = 3 * indices[j];
      var r = elements.slice(t, t + 3);
      u_sides[j] = ux * r[0] + uy * r[1] + uz * r[2] - udotstart > 0;
      v_sides[j] = vx * r[0] + vy * r[1] + vz * r[2] - vdotstart > 0;
    }

    // All vertices of the triangle are on the same side of the start point,
    // the ray cannot intersect, so we move on.
    if (((u_sides[0] == u_sides[1]) && (u_sides[0] == u_sides[2])) ||
        ((v_sides[0] == v_sides[1]) && (v_sides[0] == v_sides[2]))) {
      continue;
    }

    // Compute a matrix that transforms the unit triangle
    // (1, 0, 0)..(0, 1, 0)..(0, 0, 1) into the current triangle.
    var t;
    t = 3 * indices[0];
    var m00 = elements[t] - start[0];
    var m01 = elements[t + 1] - start[1];
    var m02 = elements[t + 2] - start[2];
    t = 3 * indices[1];
    var m10 = elements[t] - start[0];
    var m11 = elements[t + 1] - start[1];
    var m12 = elements[t + 2] - start[2];
    t = 3 * indices[2];
    var m20 = elements[t] - start[0];
    var m21 = elements[t + 1] - start[1];
    var m22 = elements[t + 2] - start[2];

    var t00 = m11 * m22 - m12 * m21;
    var t10 = m01 * m22 - m02 * m21;
    var t20 = m01 * m12 - m02 * m11;

    // Compute the determinant of the matrix.  The sign (+/-) tells us
    // if it's culled.
    var d = m00 * t00 - m10 * t10 + m20 * t20;

    if ((cull == o3d.State.CULL_CW && d < 0) ||
        (cull == o3d.State.CULL_CCW && d > 0)) {
      continue;
    }

    // Transform the direction vector by the inverse of that matrix.
    // If the end point is in the first octant, it's a hit.
    var v0 = (t00 * x -
        (m10 * m22 - m12 * m20) * y +
        (m10 * m21 - m11 * m20) * z) / d;
    var v1 = (-t10 * x +
        (m00 * m22 - m02 * m20) * y -
        (m00 * m21 - m01 * m20) * z) / d;
    var v2 = (t20 * x -
        (m00 * m12 - m02 * m10) * y +
        (m00 * m11 - m01 * m10) * z) / d;

    if (v0 >= 0 && v1 >= 0 && v2 >= 0 && (v0 + v1 + v2 > 0)) {
      // Rescale by the one-norm to find the intersection of the transformed.
      // ray with the unit triangle.
      var one_norm = v0 + v1 + v2;
      v0 /= one_norm;
      v1 /= one_norm;
      v2 /= one_norm;
      // Multiply m to get back to the original triangle.
      var px = m00 * v0 + m10 * v1 + m20 * v2;
      var py = m01 * v0 + m11 * v1 + m21 * v2;
      var pz = m02 * v0 + m12 * v1 + m22 * v2;
      // Compute the distance (actually distance squared) from the start point
      // to the intersection.
      var distance = px * px + py * py + pz * pz;
      if (!result.intersected || distance < min_distance) {
        min_distance = distance;
        result.position[0] = px + start[0];
        result.position[1] = py + start[1];
        result.position[2] = pz + start[2];
        result.primitiveIndex = i;
      }
      result.intersected = true;
    }
  }

  return result;
};


/**
 * Computes the bounding box in same coordinate system as the specified
 * POSITION stream.
 * @param {number} position_stream_index Index of POSITION stream.
 * @return {!o3d.BoundingBox}  The boundingbox for this element in local space.
 */
o3d.Primitive.prototype.getBoundingBox =
    function(position_stream_index) {
  var streamBank = this.streamBank;
  var indexBuffer = this.indexBuffer;
  var stream =
    this.streamBank.getVertexStream(o3d.Stream.POSITION, position_stream_index);

  var points = [];
  var field = stream.field;
  var buffer = field.buffer;
  var numPoints = buffer.array_.length / buffer.totalComponents;

  var elements = field.getAt(0, numPoints);

  for (var index = 0; index < numPoints; ++index) {
    var p = [0, 0, 0];
    for (var i = 0; i < field.numComponents; ++i) {
      p[i] = elements[field.numComponents * index + i];
    }
    points.push(p);
  }

  o3d.BoundingBox.fitBoxToPoints_(points, this.boundingBox);
  return this.boundingBox;
};



/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * The Shape represents a collection of Elements. The typical example is a
 * cube with 6 faces where each face uses a different material would be
 * represented as 1 Shape with 6 Elements, one for each material.
 * @constructor
 */
o3d.Shape = function() {
  o3d.ParamObject.call(this);
  this.elements = [];
};
o3d.inherit('Shape', 'ParamObject');


/**
 * The elements owned by this shape.
 *
 * Each access to this field gets the entire list so it is best to get it
 * just once. For example:
 *
 * var elements = renderNode.elements;
 * for (var i = 0; i < elements.length; i++) {
 *   var element = elements[i];
 * }
 *
 * Note that modifications to this array [e.g. push()] will not affect
 * the underlying Shape, while modifications to the array's members
 * will affect them.
 */
o3d.Shape.prototype.elements = [];


/**
 * Finds a draw element in the given list of draw elements that uses the given
 * material if such a draw element exists.  Returns null otherwise.
 * @param {Array.<!o3d.DrawElements>} drawElements An array of draw elements.
 * @param {o3d.Material} material A material to search for.
 * @private
 */
o3d.Shape.findDrawElementWithMaterial_ = function(drawElements, material) {
  for (var j = 0; j < drawElements.length; ++j) {
    if (drawElements[j].material == material) {
      return drawElements[j];
    }
  }
  return null;
};


/**
 * Creates a DrawElement for each Element owned by this Shape.
 * If an Element already has a DrawElement that uses material a new
 * DrawElement will not be created.
 * @param {o3d.Pack} pack pack used to manage created DrawElements.
 * @param {o3d.Material} material material to use for each DrawElement.
 *     Note: When a DrawElement with a material of null is rendered, the
 *     material on the corresponding Element will get used instead.
 *     This allows you to easily setup the default (just draw as is) by
 *     passing null or setup a shadow pass by passing in a shadow material.
 */
o3d.Shape.prototype.createDrawElements =
    function(pack, material) {
  var elements = this.elements;
  for (var i = 0; i < elements.length; ++i) {
    var element = elements[i];
    if (!o3d.Shape.findDrawElementWithMaterial_(element.drawElements,
                                                material)) {
      element.createDrawElement(pack, material);
    }
  }
};


/**
 * Adds and element to the list of elements for this shape.
 * @param {o3d.Element} element The element to add.
 */
o3d.Shape.prototype.addElement = function(element) {
  this.elements.push(element);
};


/**
 * Removes and element to the list of elements for this shape.
 * @param {o3d.Element} element The element to add.
 */
o3d.Shape.prototype.removeElement = function(element) {
  o3d.removeFromArray(this.elements, element);
};


/**
 * Called when the tree traversal finds this shape in the transform tree.
 * Adds objects to the given drawlists if the drawlist of the material matches.
 * @param {Array.<Object>} drawListInfos A list of objects containing
 *     drawlists and matrix info.
 * @param {o3d.math.Matrix4} world The world matrix.
 */
o3d.Shape.prototype.writeToDrawLists =
    function(drawListInfos, world, transform) {
  var elements = this.elements;

  // Iterate through elements of this shape.
  for (var i = 0; i < elements.length; ++i) {
    var element = elements[i];

    // For each element look at the DrawElements for that element.
    for (var j = 0; j < element.drawElements.length; ++j) {
      this.gl.client.render_stats_['drawElementsProcessed']++;
      var drawElement = element.drawElements[j];
      var material = drawElement.material || drawElement.owner.material;
      var materialDrawList = material.drawList;
      var rendered = false;

      // Iterate through the drawlists we might write to.
      for (var k = 0; k < drawListInfos.length; ++k) {
        var drawListInfo = drawListInfos[k];
        var list = drawListInfo.list;

        // If any of those drawlists matches the material on the drawElement,
        // add the drawElement to the list.
        if (materialDrawList == list) {
          var context = drawListInfo.context;
          var view = context.view;
          var projection = context.projection;

          var worldViewProjection = [[], [], [], []];
          var viewProjection = [[], [], [], []];
          o3d.Transform.compose(projection, view, viewProjection);
          o3d.Transform.compose(viewProjection, world, worldViewProjection);

          if (element.cull && element.boundingBox) {
            if (!element.boundingBox.inFrustum(worldViewProjection)) {
              continue;
            }
          }

          rendered = true;
          list.list_.push({
            view: view,
            projection: projection,
            world: world,
            viewProjection: viewProjection,
            worldViewProjection: worldViewProjection,
            transform: transform,
            drawElement: drawElement
          });
        }
      }

      if (rendered) {
        this.gl.client.render_stats_['drawElementsRendered']++;
      } else {
        this.gl.client.render_stats_['drawElementsCulled']++;
      }
    }
  }
};

/**
 * Calculate the bounding box of the Shape based upon its Elements.
 * 
 * @param {boolean} opt_force optional flag indicating that Primitives should be
 *     forced to recalculate their bounding box even if they think it is valid
 * @return {o3d.BoundingBox} the bounding box of the Shape
 */
o3d.Shape.prototype.getBoundingBox = function(force) {
	var elements = this.elements,
		box = new o3d.BoundingBox();
	
	for (var i = 0, il = elements.length; i < il; ++i) {
		var element = elements[i],
			elementBox = element.boundingBox;
		if (force || !elementBox.valid) {
			elementBox = element.getBoundingBox(0);
		}
		if (box.valid) {
			box = box.add(elementBox);
		} else {
			box = elementBox;
		}
	}
	
	return box;
};
/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * EffectParameterInfo holds information about the Parameters an Effect needs.
 * o3d.Effect.getParameterInfo
 * @param {string} name Tne name of the parameter.
 * @param {string} className The param class name.
 * @param {number} numElements The number of Elements if the param is an array.
 * @param {string} sasClassName The sas class name if the param is an sas type.
 * @param {string} semantic The relevant semantic.
 */
o3d.EffectParameterInfo =
    function(name, className, numElements, semantic, sasClassName) {
  /**
   * The name of the parameter.
   * @type {string}
   */
  this.name = name || '';

  /**
   * The type of the parameter.
   * @type {string}
   */
  this.className = className || '';

  /**
   * The number of elements.  Non-zero for array types, zero for non-array
   * types.
   * @type {number}
   */
  this.numElements = numElements || 0;

  /**
   * The semantic of the parameter. This is always in UPPERCASE.
   * @type {string}
   */
  this.semantic = semantic || '';

  /**
   * If this is a standard parameter (SAS) this will be the name of the type
   * of Param needed. Otherwise it will be the empty string.
   *
   * Standard Parameters are generally handled automatically by o3d but you
   * can supply your own if you have a unique situation.
   *
   * @type {string}
   */
  this.sasClassName = sasClassName || '';
};
o3d.inherit('EffectParameterInfo', 'NamedObject');


/**
 * EffectStreamInfo holds information about the Streams an Effect needs.
 * @param {o3d.Stream.Semantic} opt_semantic The semantic of the stream
 * @param {number} opt_semantic_index
 * @constructor
 */
o3d.EffectStreamInfo = function(opt_semantic, opt_semantic_index) {
  o3d.NamedObject.call(this);
  if (!opt_semantic) {
    opt_semantic = o3d.Stream.UNKNOWN_SEMANTIC;
  }
  if (!opt_semantic_index) {
    opt_semantic_index = 0;
  }
  this.semantic = opt_semantic;
  this.opt_semantic_index = opt_semantic_index;
};
o3d.inherit('EffectStreamInfo', 'NamedObject');


/**
 * The semantic of the stream.
 * @type {!o3d.Stream.Semantic}
 */
o3d.EffectStreamInfo.prototype.semantic = o3d.Stream.UNKNOWN_SEMANTIC;



/**
 * The semantic index of the stream.
 * @type {number}
 */
o3d.EffectStreamInfo.prototype.semanticIndex = 0;


/**
 * An Effect contains a vertex and pixel shader.
 * @constructor
 */
o3d.Effect = function() {
  o3d.ParamObject.call(this);
  this.program_ = null;
  this.uniforms_ = {};
  this.attributes_ = {};
};
o3d.inherit('Effect', 'ParamObject');

o3d.Effect.HELPER_CONSTANT_NAME = 'dx_clipping';


/**
 * An object mapping the names of uniform variables to objects containing
 * information about the variable.
 * @type {Object}
 * @private
 */
o3d.Effect.prototype.uniforms_ = {};


/**
 * An object mapping the names of attributes to objects containing
 * information about the attribute.
 * @type {Object}
 * @private
 */
o3d.Effect.prototype.attributes_ = {};


/**
 * Indicates whether the vertex shader has been loaded, so we can
 * postpone linking until both shaders are in.
 *
 * @type {boolean}
 */
o3d.Effect.prototype.vertexShaderLoaded_ = false;


/**
 * Indicates whether the fragment shader has been loaded, so we can
 * postpone linking until both shaders are in.
 *
 * @type {boolean}
 */
o3d.Effect.prototype.fragmentShaderLoaded_ = false;


/**
 * Binds standard attribute locations for the shader.
 */
o3d.Effect.prototype.bindAttributesAndLinkIfReady = function() {
  if (this.vertexShaderLoaded_ && this.fragmentShaderLoaded_) {
    var semanticMap = o3d.Effect.semanticMap_;
    for (var name in semanticMap) {
      this.gl.bindAttribLocation(
          this.program_, semanticMap[name].gl_index, name);
    }
    this.gl.linkProgram(this.program_);
    if (!this.gl.getProgramParameter(this.program_, this.gl.LINK_STATUS)) {
      var log = this.gl.getShaderInfoLog(this.program_);
      this.gl.client.error_callback(
          'Program link failed with error log:\n' + log);
    }
    this.getUniforms_();
    this.getAttributes_();
  }
};


/**
 * Helper function for loadVertexShaderFromString and
 * loadPixelShaderFromString that takes the type as an argument.
 * @param {string} shaderString The shader code.
 * @param {number} type The type of the shader: either
 *    VERTEX_SHADER or FRAGMENT_SHADER.
 * @return {bool} Success.
 */
o3d.Effect.prototype.loadShaderFromString = function(shaderString, type) {
  if (!this.program_) {
    this.program_ = this.gl.createProgram();
  }

  var success = true;

  var shader = this.gl.createShader(type);
  this.gl.shaderSource(
      shader, "#ifdef GL_ES\nprecision highp float;\n#endif\n" + shaderString);
  this.gl.compileShader(shader);
  if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
    success = false;
    var log = this.gl.getShaderInfoLog(shader);
    this.gl.client.error_callback(
        'Shader compile failed with error log:\n' + log);
  }

  this.gl.attachShader(this.program_, shader);

  return success;
};


/**
 * Loads a glsl vertex shader for this effect from a string.
 * @param {string} shaderString The string.
 * @return {bool} Success.
 */
o3d.Effect.prototype.loadVertexShaderFromString =
    function(shaderString) {
  var success =
      this.loadShaderFromString(shaderString, this.gl.VERTEX_SHADER);
  this.vertexShaderLoaded_ = success;
  this.bindAttributesAndLinkIfReady();
  return success;
};


/**
 * Loads a glsl vertex shader for this effect from a string.
 * @param {string} shaderString The string.
 * @return {bool} Success.
 */
o3d.Effect.prototype.loadPixelShaderFromString =
    function(shaderString) {
  var success =
      this.loadShaderFromString(shaderString, this.gl.FRAGMENT_SHADER);
  this.fragmentShaderLoaded_ = success;
  this.bindAttributesAndLinkIfReady();
  return success;
};


/**
 * Loads a glsl vertex shader and pixel shader from one string.
 * Assumes the vertex shader and pixel shader are separated by
 * the text '// #o3d SplitMarker'.
 * @param {string} shaderString The string.
 * @return {bool} Success.
 */
o3d.Effect.prototype.loadFromFXString =
    function(shaderString) {
  var splitIndex = shaderString.indexOf('// #o3d SplitMarker');
  return this.loadVertexShaderFromString(shaderString.substr(0, splitIndex)) &&
      this.loadPixelShaderFromString(shaderString.substr(splitIndex));
};


/**
 * Generates an array of indexed strings. For example, given 'arr' and a size
 * of 10, generates 'arr[0]', 'arr[1]', 'arr[2]' up to 'arr[9]'.
 *
 * @param {string} base The name of the array.
 * @param {number} size The number of elements in the array.
 * @return {!Array.<string>}
 * @private
 */
o3d.Effect.prototype.getParamArrayNames_ = function(base, size) {
  var names = [];
  for (var i = 0; i < size; i++) {
    names[i] = base + '[' + i + ']';
  }
  return names;
}


/**
 * Iterates through the active uniforms of the program and gets the
 * location of each one and stores them by name in the uniforms
 * object.
 * @private
 */
o3d.Effect.prototype.getUniforms_ =
    function() {
  this.uniforms_ = {};
  var numUniforms = this.gl.getProgramParameter(
      this.program_, this.gl.ACTIVE_UNIFORMS);
  for (var i = 0; i < numUniforms; ++i) {
    var info = this.gl.getActiveUniform(this.program_, i);
    var name = info.name;
    if (name.indexOf('[') != -1) {
      // This is an array param and we need to individually query each item in
      // the array to get its location.
      var baseName = info.name.substring(0, info.name.indexOf('['));
      var names = this.getParamArrayNames_(baseName, info.size);
      var locations = [];
      for (var j = 0; j < names.length; j++) {
        locations[j] = this.gl.getUniformLocation(this.program_, names[j]);
      }
      this.uniforms_[baseName] = {
          info: {name: baseName, size: info.size, type: info.type},
          kind: o3d.Effect.ARRAY,
          locations: locations /* mind the s */
      };
    } else {
      // Not an array param.
      this.uniforms_[name] = {
          info: info,
          kind: o3d.Effect.ELEMENT,
          location: this.gl.getUniformLocation(this.program_, name)
      };
    }
  }
};


/**
 * Iterates through the active attributes of the program and gets the
 * location of each one and stores them by name in the attributes
 * object.
 * @private
 */
o3d.Effect.prototype.getAttributes_ =
    function() {
  this.attributes_ = {};
  var numAttributes = this.gl.getProgramParameter(
      this.program_, this.gl.ACTIVE_ATTRIBUTES);
  for (var i = 0; i < numAttributes; ++i) {
    var info = this.gl.getActiveAttrib(this.program_, i);
    this.attributes_[info.name] = {info:info,
        location:this.gl.getAttribLocation(this.program_, info.name)};
  }
};


/**
 * A map linking the WebGL type of a uniform to the appropriate param type.
 * This gets memoized in the function createUniformParameters.
 * @private
 */
o3d.Effect.paramTypes_ = null;

/**
 * Accesses the paramTypes map, builds it if it isn't already built.
 * @private;
 */
o3d.Effect.getParamTypes_ = function(gl)  {
  // Even though these constants should be the same for different webgl
  // contexts, we can't create this table until the context is loaded, so
  // we initialize it here rather than when the file loads.
  if (!o3d.Effect.paramTypes_) {
    o3d.Effect.paramTypes_ = {};
    o3d.Effect.paramTypes_[gl.FLOAT] = 'ParamFloat';
    o3d.Effect.paramTypes_[gl.FLOAT_VEC2] = 'ParamFloat2';
    o3d.Effect.paramTypes_[gl.FLOAT_VEC3] = 'ParamFloat3';
    o3d.Effect.paramTypes_[gl.FLOAT_VEC4] = 'ParamFloat4';
    o3d.Effect.paramTypes_[gl.INT] = 'ParamInteger';
    o3d.Effect.paramTypes_[gl.BOOL] = 'ParamBoolean';
    o3d.Effect.paramTypes_[gl.FLOAT_MAT4] = 'ParamMatrix4';
    o3d.Effect.paramTypes_[gl.SAMPLER_2D] = 'ParamSampler';
    o3d.Effect.paramTypes_[gl.SAMPLER_CUBE] = 'ParamSampler';
  }

  return o3d.Effect.paramTypes_;
}


/**
 * A map linking names of certain attributes in the shader to the corresponding
 * semantic and semantic index.
 * @private
 */
o3d.Effect.semanticMap_ = {
  'position': {semantic: o3d.Stream.POSITION, index: 0, gl_index: 0},
  'normal': {semantic: o3d.Stream.NORMAL, index: 0, gl_index: 1},
  'tangent': {semantic: o3d.Stream.TANGENT, index: 0, gl_index: 2},
  'binormal': {semantic: o3d.Stream.BINORMAL, index: 0, gl_index: 3},
  'color': {semantic: o3d.Stream.COLOR, index: 0, gl_index: 4},
  'texCoord0': {semantic: o3d.Stream.TEXCOORD, index: 0, gl_index: 5},
  'texCoord1': {semantic: o3d.Stream.TEXCOORD, index: 1, gl_index: 6},
  'texCoord2': {semantic: o3d.Stream.TEXCOORD, index: 2, gl_index: 7},
  'texCoord3': {semantic: o3d.Stream.TEXCOORD, index: 3, gl_index: 8},
  'texCoord4': {semantic: o3d.Stream.TEXCOORD, index: 4, gl_index: 9},
  'texCoord5': {semantic: o3d.Stream.TEXCOORD, index: 5, gl_index: 10},
  'texCoord6': {semantic: o3d.Stream.TEXCOORD, index: 6, gl_index: 11},
  'texCoord7': {semantic: o3d.Stream.TEXCOORD, index: 7, gl_index: 12},
  'influenceWeights': {semantic: o3d.Stream.INFLUENCE_WEIGHTS, index: 0,
                       gl_index: 13},
  'influenceIndices': {semantic: o3d.Stream.INFLUENCE_INDICES, index: 0,
                       gl_index: 14}
};

o3d.Effect.reverseSemanticMap_ = [];
(function(){
  var revmap = o3d.Effect.reverseSemanticMap_;
  for (var key in o3d.Effect.semanticMap_) {
    var value = o3d.Effect.semanticMap_[key];
    revmap[value.semantic] = revmap[value.semantic] || [];
    revmap[value.semantic][value.index] = value.gl_index;
  }
})();


/**
 * For each of the effect's uniform parameters, creates corresponding
 * parameters on the given ParamObject. Skips SAS Parameters.
 *
 * If a Param with the same name but the wrong type already exists on the
 * given ParamObject createUniformParameters will attempt to replace it with
 * one of the correct type.
 *
 * Note: The most common thing to pass to this function is a Material but
 * depending on your application it may be more appropriate to pass in a
 * Transform, Effect, Element or DrawElement.
 *
 * @param {!o3d.ParamObject} param_object The param object on which the
 *     new paramters will be created.
 */
o3d.Effect.prototype.createUniformParameters =
    function(param_object) {
  var sasTypes = o3d.Param.sasTypes_;
  var paramTypes = o3d.Effect.getParamTypes_(this.gl);
  for (var name in this.uniforms_) {
    var uniformData = this.uniforms_[name];
    if (!sasTypes[name]) {
      switch (uniformData.kind) {
        case o3d.Effect.ARRAY:
          var param = param_object.createParam(name, 'ParamParamArray');
          var array = new o3d.ParamArray;
          array.gl = this.gl;
          array.resize(uniformData.info.size,
              paramTypes[uniformData.info.type]);
          param.value = array;
          break;
        case o3d.Effect.STRUCT:
          o3d.notImplemented();
          break;
        case o3d.Effect.ELEMENT:
        default:
          param_object.createParam(name, paramTypes[uniformData.info.type]);
          break;
      }
    }
  }
};


/**
 * For each of the effect's uniform parameters, if it is a SAS parameter
 * creates corresponding StandardParamMatrix4 parameters on the given
 * ParamObject.  Note that SAS parameters are handled automatically by the
 * rendering system. so except in some rare cases there is no reason to call
 * this function.  Also be aware that the StandardParamMatrix4 Paramters like
 * WorldViewProjectionParamMatrix4, etc.. are only valid during rendering.
 * At all other times they will not return valid values.
 *
 * If a Param with the same name but the wrong type already exists on the
 * given ParamObject CreateSASParameters will attempt to replace it with
 * one of the correct type.
 *
 * @param {!o3d.ParamObject} param_object The param object on which the new
 *     paramters will be created.
 */
o3d.Effect.prototype.createSASParameters =
    function(param_object) {
  var sasTypes = o3d.Param.sasTypes_;
  for (var name in this.uniforms_) {
    var info = this.uniforms_[name].info;
    var sasType = sasTypes[name];
    if (sasType) {
      param_object.createParam(info.name, sasType);
    }
  }
};


/**
 * Gets info about the parameters this effect needs.
 * @return {!Array.<!o3d.EffectParameterInfo>} an array of
 *     EffectParameterInfo objects.
 */
o3d.Effect.prototype.getParameterInfo = function() {
  var infoArray = [];
  var sasTypes = o3d.Param.sasTypes_;
  var semanticMap = o3d.Effect.semanticMap_;
  var paramTypes = o3d.Effect.getParamTypes_(this.gl);

  for (var name in this.uniforms_) {
    var uniformData = this.uniforms_[name];
    var sasClassName = sasTypes[name] || '';
    var dataType = paramTypes[uniformData.info.type] || '';
    var numElements = (uniformData.kind == o3d.Effect.ARRAY) ?
        uniformData.info.size : 0; // 0 if a non-array type.
    var semantic = semanticMap[name] ? name : '';
    infoArray.push(new o3d.EffectParameterInfo(
      name, dataType, numElements, semantic.toUpperCase(), sasClassName));
  }

  return infoArray;
};


/**
 * Gets info about the streams this effect needs.
 * @return {!Array.<!o3d.EffectStreamInfo>} an array of
 *     EffectStreamInfo objects.
 */
o3d.Effect.prototype.getStreamInfo = function() {
  var infoList = [];

  for (var name in this.attributes_) {
    var semantic_index_pair = o3d.Effect.semanticMap_[name];
    infoList.push(new o3d.EffectStreamInfo(
        semantic_index_pair.semantic, semantic_index_pair.index));
  }
  return infoList;
};


/**
 * Searches the objects in the given list for parameters to apply to the
 * uniforms defined on this effects program, and applies them, favoring
 * the objects nearer the beginning of the list.
 *
 * @param {!Array.<!o3d.ParamObject>} object_list The param objects to search.
 * @private
 */
o3d.Effect.prototype.searchForParams_ = function(object_list) {
  var unfilledMap = {};
  for (var uniformName in this.uniforms_) {
    unfilledMap[uniformName] = true;
  }
  this.gl.useProgram(this.program_);
  o3d.Param.texture_index_ = 0;
  var object_list_length = object_list.length;
  for (var i = 0; i < object_list_length; ++i) {
    var obj = object_list[i];
    for (var name in this.uniforms_) {
      if (unfilledMap[name]) {
        var uniformInfo = this.uniforms_[name];
        var param = obj.getParam(name);
        if (param) {
          if (uniformInfo.kind == o3d.Effect.ARRAY) {
            param.applyToLocations(this.gl, uniformInfo.locations);
          } else {
            param.applyToLocation(this.gl, uniformInfo.location);
          }
          delete unfilledMap[name];
        }
      }
    }
  }

  this.updateHelperConstants_(this.gl.displayInfo.width,
                              this.gl.displayInfo.height);
  delete unfilledMap[o3d.Effect.HELPER_CONSTANT_NAME];
  for (var name in unfilledMap) {
    if (this.uniforms_[name].info.type == this.gl.SAMPLER_2D) {
      if (this.gl.client.reportErrors_()) {
        this.gl.client.error_callback("Missing ParamSampler");
      }
      var defaultParamSampler = o3d.ParamSampler.defaultParamSampler_;
      defaultParamSampler.gl = this.gl;
      defaultParamSampler.applyToLocation(this.gl,
          this.uniforms_[name].location);
    } else {
      throw ('Uniform param not filled: "'+ name + '"');
    }
  }
};


/**
 * Updates certain parameters used to make the GLSL shaders have the
 * same clipping semantics as D3D's.
 * @param {number} width width of the viewport in pixels
 * @param {number} height height of the viewport in pixels
 * @private
 */
o3d.Effect.prototype.updateHelperConstants_ = function(width, height) {
  var uniformInfo = this.uniforms_[o3d.Effect.HELPER_CONSTANT_NAME];
  var dx_clipping = [ 0.0, 0.0, 0.0, 0.0 ];
  if (uniformInfo) {
    // currentRenderSurfaceSet is set in render_surface_set.js.
    dx_clipping[0] = 1.0 / width;
    dx_clipping[1] = -1.0 / height;
    dx_clipping[2] = 2.0;
    if (this.gl.currentRenderSurfaceSet) {
      dx_clipping[3] = -1.0;
    } else {
      dx_clipping[3] = 1.0;
    }

    this.gl.uniform4f(uniformInfo.location,
                      dx_clipping[0], dx_clipping[1],
                      dx_clipping[2], dx_clipping[3]);
  }
};

/**
 * @type {number}
 */
o3d.Effect.MatrixLoadOrder = goog.typedef;

/**
 *  MatrixLoadOrder,
 *  ROW_MAJOR,  Matrix parameters are loaded in row-major order (DX-style).
 *  COLUMN_MAJOR,   Matrix parameters are loaded in column-major order
 *     (OpenGL-style).
 */
o3d.Effect.ROW_MAJOR = 0;
o3d.Effect.COLUMN_MAJOR = 1;


/**
 * UniformType,
 * ELEMENT,   the param is a single gl.* element
 * ARRAY,   the param is an array of same-typed elements
 * STRUCT,   not implemented
 */
o3d.Effect.ELEMENT = 0;
o3d.Effect.ARRAY = 1;
o3d.Effect.STRUCT = 2;


/**
 * The order in which matrix data is loaded to the GPU.
 * @type {o3d.Effect.MatrixLoadOrder}
 */
o3d.Effect.prototype.matrix_load_order_ = o3d.Effect.ROW_MAJOR;


/**
 * The source for the shaders on this Effect.
 * @type {string}
 */
o3d.Effect.prototype.source_ = '';
/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * A Material holds the various uniform parameters an Effect needs to render.
 * For example a Lambert effect might need "diffuse", "ambient", "emissive".
 * The parameters needed on a Material will vary depending its Effect.
 * Note that a material MUST have its drawList set in order for objects using it
 * to render.
 *
 * @param {!o3d.State} opt_state The State used by this material.
 * @param {!o3d.Effect} opt_effect The Effect used by this material.
 * @param {!o3d.DrawList} opt_draw_list The the DrawList used by this material.
 * @constructor
 */
o3d.Material = function(opt_state, opt_effect, opt_draw_list) {
  o3d.ParamObject.call(this);
  /**
   * The State for this material.
   * @type {o3d.State}
   */
  this.state = opt_state || null;

  /**
   * The Effect for this material.
   * @type {o3d.Effect}
   */
  this.effect = opt_effect || null;

  /**
   * The DrawList this material will render on.
   * @type {o3d.DrawList}
   */
  this.drawList = opt_draw_list || null;
};
o3d.inherit('Material', 'ParamObject');

o3d.ParamObject.setUpO3DParam_(o3d.Material, 'effect', 'ParamEffect');
o3d.ParamObject.setUpO3DParam_(o3d.Material, 'state', 'ParamState');
o3d.ParamObject.setUpO3DParam_(o3d.Material, 'drawList', 'ParamDrawList');

/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

// TODO(kbr): figure out how we can reuse the o3djs.io package from
// within here.
// // TODO(kbr): factor this out into e.g. o3djs.json and require
// o3djs.json here.
if(!this.JSON){this.JSON={};}
(function(){function f(n){return n<10?'0'+n:n;}
if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(key){return isFinite(this.valueOf())?this.getUTCFullYear()+'-'+
f(this.getUTCMonth()+1)+'-'+
f(this.getUTCDate())+'T'+
f(this.getUTCHours())+':'+
f(this.getUTCMinutes())+':'+
f(this.getUTCSeconds())+'Z':null;};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf();};}
var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==='string'?c:'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4);})+'"':'"'+string+'"';}
function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==='object'&&typeof value.toJSON==='function'){value=value.toJSON(key);}
if(typeof rep==='function'){value=rep.call(holder,key,value);}
switch(typeof value){case'string':return quote(value);case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';}
gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==='[object Array]'){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||'null';}
v=partial.length===0?'[]':gap?'[\n'+gap+
partial.join(',\n'+gap)+'\n'+
mind+']':'['+partial.join(',')+']';gap=mind;return v;}
if(rep&&typeof rep==='object'){length=rep.length;for(i=0;i<length;i+=1){k=rep[i];if(typeof k==='string'){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}else{for(k in value){if(Object.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}
v=partial.length===0?'{}':gap?'{\n'+gap+partial.join(',\n'+gap)+'\n'+
mind+'}':'{'+partial.join(',')+'}';gap=mind;return v;}}
if(typeof JSON.stringify!=='function'){JSON.stringify=function(value,replacer,space){var i;gap='';indent='';if(typeof space==='number'){for(i=0;i<space;i+=1){indent+=' ';}}else if(typeof space==='string'){indent=space;}
rep=replacer;if(replacer&&typeof replacer!=='function'&&(typeof replacer!=='object'||typeof replacer.length!=='number')){throw new Error('JSON.stringify');}
return str('',{'':value});};}
if(typeof JSON.parse!=='function'){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==='object'){for(k in value){if(Object.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v;}else{delete value[k];}}}}
return reviver.call(holder,key,value);}
text=String(text);cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return'\\u'+
('0000'+a.charCodeAt(0).toString(16)).slice(-4);});}
if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+text+')');return typeof reviver==='function'?walk({'':j},''):j;}
throw new SyntaxError('JSON.parse');};}}());


/**
  An ArchiveRequest object is used to carry out an asynchronous request for a
  compressed archive (containing multiple files).

  Note: The archive must have as its first file a file named 'aaaaaaaa.o3d'
  who's contents is 'o3d'. This is to prevent O3D being used to open
  archive files that were not meant for it.

  \code
  var request = pack.createArchiveRequest();
  request.open("GET", url);

  request.onfileavailable = myFileAvailableCallback;
  request.onreadystatechange = myReadyStateChangeCallback;
  request.send();

  function myFileAvailableCallback(rawData) {
    dump("uri: " + rawData.uri + "\n");
    dump("content: " + rawData.stringValue + "\n");

    // You can pass a RawData to various creation functions. Note: rawData
    // is only valid until you remove the request.
    // Examples:
    if (rawData.uri == 'mytexture.jpg')
      pack.createTexture2d(rawData, makeMips);
    if (rawData.uri == 'myvertices.bin')
      vertexBuffer.set(rawData);
    if (rawData.uri == 'myAudio.mp3')
      audioSystem.createSound(rawData);
  }

  function myReadyStateChangeCallback() {
    if (request.done) {
      if (request.success) {
        // there were no errors trying to read the archive
      } else {
        dump(request.error);
      }
    }
  }

  // When you are done with the RawDatas loaded by the request, remove
  // the request from the pack to free them.
  pack.removeObject(request);
*/

o3d.ArchiveRequest = function() {
  o3d.ObjectBase.call(this);
  this.method_ = null;
  this.listeners = [];
};
o3d.inherit('ArchiveRequest', 'ObjectBase');

/**
 * The URI this request is for.
 * @type {string}
 */
o3d.ArchiveRequest.prototype.uri = '';

/**
 * Set up several of the request fields.
 * @param {string} method "GET" is the only supported method at this time
 * @param {string} uri the location of the file to fetch
 * @param {boolean} async true is the only legal value at this time.
 */
o3d.ArchiveRequest.prototype.open =
    function(method, uri) {
  this.uri = uri;

  // Compute the parent directory of this URI.
  var parentURI = uri;
  var lastSlash = uri.lastIndexOf('/');
  if (lastSlash != -1) {
    parentURI = parentURI.substring(0, lastSlash + 1);
  }

  this.parentURI_ = parentURI;
};

/**
 * Sends the request.  In this implementation, this function sets up a callback
 * which searches the json it loads for more files to load.  It then sends
 * requests for each of those files.  When the last of those files has loaded,
 * the onreadystatechange callback will get called.  Unlike XMLHttpRequest the
 * onreadystatechange callback will be called no matter what, with success or
 * failure.
 */
o3d.ArchiveRequest.prototype.send = function() {
  var that = this;
  this.done = false;
  this.success = true;
  this.error = null;
	
  var onprogress = function(evt) {
  	var lst = that.listeners;
  	for (var i = 0, il = lst.length; i < il; i++) {
  	  if (lst.onProgressUpdate) {
  		lst[i].onProgressUpdate(evt);
      }
      else {
  		lst[i](evt);
  	  }
    }
  };
	
  var callback = function(sourceJSON, exc) {
    if (exc != null) {
		that.pendingRequests_ = 1;
		that.resolvePendingRequest_(null, exc);
		return;
	}
	// Don't send down the original scene.json because 'eval' is used
    // elsewhere to reconstitute it, which is risky.
    var filteredJSON = JSON.stringify(JSON.parse(sourceJSON));
    var rawData = new o3d.RawData();
    rawData.uri = 'scene.json';
    rawData.stringValue = filteredJSON;

    // In o3d-webgl, the "archive" is really just the top-level
    // scene.json. We run a regexp on it to find URIs for certain
    // well-known file types (.fx, .png, .jpg) and issue file requests
    // for them.
    var uriRegex = /\"([^\"]*\.(fx|png|jpg))\"/g;
    var matchArray;
    var uris = [];
    while ((matchArray = uriRegex.exec(sourceJSON)) != null) {
      uris.push(matchArray[1]);
    }
	
    // Plus one for the current request.
    that.pendingRequests_ = uris.length + 1;
	
    var rawFunc = function(uri) {
      var completion = function(value, exc) {
        var rawData = null;
        if (exc == null) {
          rawData = new o3d.RawData();
          rawData.uri = uri;
          rawData.stringValue = value;
        }
        that.resolvePendingRequest_(rawData, exc);
      };
      o3djs.io.loadTextFile(that.relativeToAbsoluteURI_(uri),
                            completion, onprogress);  
    };
    var imgFunc = function(uri) {
      var image = new Image();
      image.onload = function() {
        var rawData = new o3d.RawData();
        rawData.uri = uri;
        rawData.image_ = image;
        that.resolvePendingRequest_(rawData, exc);
      };
      image.onerror = function() {
        that.resolvePendingRequest_(null, exc);
      }
      image.src = that.relativeToAbsoluteURI_(uri);
    };

    // Issue requests for each of these URIs.
    for (var ii = 0; ii < uris.length; ++ii) {
      if (that.stringEndsWith_(uris[ii], ".fx")) {
        rawFunc(uris[ii]);
      } else if (that.stringEndsWith_(uris[ii], ".png") ||
                 that.stringEndsWith_(uris[ii], ".jpg")) {
        imgFunc(uris[ii]);
      }
    }

    that.resolvePendingRequest_(rawData);
  };

  o3djs.io.loadTextFile(this.uri, callback, onprogress);
};

/**
 * A callback to call whenever the ready state of the request changes.
 * @type {function(): void}
 */
o3d.ArchiveRequest.prototype.onreadystatechange = null;

o3d.ArchiveRequest.prototype.addProgressListener = function(listener) {
	this.listeners.push(listener);
};

/**
 * A callback to call when each file comes in.
 * @type {function(!o3d.RawData): void}
 */
o3d.ArchiveRequest.prototype.onfileavailable = null;

/**
 * Converts a local URI to an absolute URI.
 * @private
 */
o3d.ArchiveRequest.prototype.relativeToAbsoluteURI_ = function(relativeURI) {
  return this.parentURI_ + relativeURI;
};

/**
 * Indicates whether one string ends with another.
 * @private
 */
o3d.ArchiveRequest.prototype.stringEndsWith_ = function(string, suffix) {
  return string.substring(string.length - suffix.length) == suffix;
};

/**
 * Decrements the number of pending requests.  Calls onfileavailable callback
 * if one is provided, calls onreadystatechange if this is the last of the
 * requests.
 * @param {o3d.RawData} rawData The current raw data object.
 * @param {Object} opt_exc An optional exception.
 * @private
 */
o3d.ArchiveRequest.prototype.resolvePendingRequest_ =
    function(rawData, opt_exc) {
  this.success = this.success && rawData && (!opt_exc);
  if (opt_exc != null) {
    this.error = "" + opt_exc;
  }
  if (rawData && this.onfileavailable) {
    this.onfileavailable(rawData);
  }
  if (--this.pendingRequests_ == 0) {
    this.done = true;
    if (this.onreadystatechange) {
      this.onreadystatechange();
    }
  }
};
/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * Acts like ParamFloat, but has an unsettable value which always calls
 * this.owner_.updateOutputs to find the updated output value.
 * @constructor
 * @extends {o3d.ParamFloat}
 */
o3d.ParamFloatOutput = function() {
  o3d.ParamFloat.call(this);
};
o3d.inherit('ParamFloatOutput', 'ParamFloat');
o3d.ParamFloatOutput.prototype.__defineGetter__("value",
    function() {
      return this.owner_.updateOutputs();
    }
);
o3d.ParamFloatOutput.prototype.__defineSetter__("value",
    function(value) {}
);

/**
 * Acts like ParamFloat2, but has an unsettable value which always calls
 * this.owner_.updateOutputs to find the updated output value.
 * @constructor
 * @extends {o3d.ParamFloat2}
 */
o3d.ParamFloat2Output = function() {
  o3d.ParamFloat2.call(this);
};
o3d.inherit('ParamFloat2Output', 'ParamFloat2');
o3d.ParamFloat2Output.prototype.__defineGetter__("value",
    function() {
      return this.owner_.updateOutputs();
    }
);
o3d.ParamFloat2Output.prototype.__defineSetter__("value",
    function(value) {}
);

/**
 * Acts like ParamFloat3, but has an unsettable value which always calls
 * this.owner_.updateOutputs to find the updated output value.
 * @constructor
 * @extends {o3d.ParamFloat3}
 */
o3d.ParamFloat3Output = function() {
  o3d.ParamFloat3.call(this);
};
o3d.inherit('ParamFloat3Output', 'ParamFloat3');
o3d.ParamFloat3Output.prototype.__defineGetter__("value",
    function() {
      return this.owner_.updateOutputs();
    }
);
o3d.ParamFloat3Output.prototype.__defineSetter__("value",
    function(value) {}
);

/**
 * Acts like ParamFloat4, but has an unsettable value which always calls
 * this.owner_.updateOutputs to find the updated output value.
 * @constructor
 * @extends {o3d.ParamFloat4}
 */
o3d.ParamFloat4Output = function() {
  o3d.ParamFloat4.call(this);
};
o3d.inherit('ParamFloat4Output', 'ParamFloat4');
o3d.ParamFloat4Output.prototype.__defineGetter__("value",
    function() {
      return this.owner_.updateOutputs();
    }
);
o3d.ParamFloat4Output.prototype.__defineSetter__("value",
    function(value) {}
);

/**
 * Acts like ParamMatrix4, but has an unsettable value which always calls
 * this.owner_.updateOutputs to find the updated output value.
 * @constructor
 * @extends {o3d.ParamMatrix4}
 */
o3d.ParamMatrix4Output = function() {
  o3d.ParamMatrix4.call(this);
};
o3d.inherit('ParamMatrix4Output', 'ParamMatrix4');
o3d.ParamMatrix4Output.prototype.__defineGetter__("value",
    function() {
      return this.owner_.updateOutputs();
    }
);
o3d.ParamMatrix4Output.prototype.__defineSetter__("value",
    function(value) {}
);

/**
 * A Param operation that takes 2 floats to produce a float2.
 * @constructor
 * @extends {o3d.ParamObject}
 */
o3d.ParamOp2FloatsToFloat2 = function() {
  o3d.ParamObject.call(this);
  this.last_output_value_ = [0, 0];
};
o3d.inherit('ParamOp2FloatsToFloat2', 'ParamObject');

(function(){
  for (var i = 0; i < 2; i++) {
    o3d.ParamObject.setUpO3DParam_(
        o3d.ParamOp2FloatsToFloat2, "input"+i, "ParamFloat");
  }
  o3d.ParamObject.setUpO3DParam_(
      o3d.ParamOp2FloatsToFloat2, "output", "ParamMatrix4Output");
})();

/**
 * Called by o3d.Param*Output whenever its value gets read.
 * @return {!Array.<number>} 2-element array equal to [input0,input1]
 */
o3d.ParamOp2FloatsToFloat2.prototype.updateOutputs = function() {
  this.last_output_value_[0] = this.getParam("input0").value;
  this.last_output_value_[1] = this.getParam("input1").value;
  return this.last_output_value_;
};

/**
 * A Param operation that takes 3 floats to produce a float3.
 * @constructor
 * @extends {o3d.ParamObject}
 */
o3d.ParamOp3FloatsToFloat3 = function() {
  o3d.ParamObject.call(this);
  this.last_output_value_ = [0, 0, 0];
};
o3d.inherit('ParamOp3FloatsToFloat3', 'ParamObject');

(function(){
  for (var i = 0; i < 3; i++) {
    o3d.ParamObject.setUpO3DParam_(
        o3d.ParamOp3FloatsToFloat3, "input"+i, "ParamFloat");
  }
  o3d.ParamObject.setUpO3DParam_(
      o3d.ParamOp3FloatsToFloat3, "output", "ParamMatrix4Output");
})();

/**
 * Called by o3d.Param*Output whenever its value gets read.
 * @return {!Array.<number>} 3-element array equal to [input0,input1,input2]
 */
o3d.ParamOp3FloatsToFloat3.prototype.updateOutputs = function() {
  this.last_output_value_[0] = this.getParam("input0").value;
  this.last_output_value_[1] = this.getParam("input1").value;
  this.last_output_value_[2] = this.getParam("input2").value;
  return this.last_output_value_;
};

/**
 * A Param operation that takes 4 floats to produce a float4.
 * @constructor
 * @extends {o3d.ParamObject}
 */
o3d.ParamOp4FloatsToFloat4 = function() {
  o3d.ParamObject.call(this);
  this.last_output_value_ = [0, 0, 0, 0];
};
o3d.inherit('ParamOp4FloatsToFloat4', 'ParamObject');

(function(){
  for (var i = 0; i < 4; i++) {
    o3d.ParamObject.setUpO3DParam_(
        o3d.ParamOp4FloatsToFloat4, "input"+i, "ParamFloat");
  }
  o3d.ParamObject.setUpO3DParam_(
      o3d.ParamOp4FloatsToFloat4, "output", "ParamMatrix4Output");
})();

/**
 * Called by o3d.Param*Output whenever its value gets read.
 * @return {!Array.<number>} 4-element array equal to
 *     [input0,input1,input2,input3]
 */
o3d.ParamOp4FloatsToFloat4.prototype.updateOutputs = function() {
  this.last_output_value_[0] = this.getParam("input0").value;
  this.last_output_value_[1] = this.getParam("input1").value;
  this.last_output_value_[2] = this.getParam("input2").value;
  this.last_output_value_[3] = this.getParam("input3").value;
  return this.last_output_value_;
};

/**
 * A Param operation that takes 16 floats to produce a 4-by-4 matrix.
 * @constructor
 * @extends {o3d.ParamObject}
 */
o3d.ParamOp16FloatsToMatrix4 = function() {
  o3d.ParamObject.call(this);
  this.last_output_value_ =
      [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];
};
o3d.inherit('ParamOp16FloatsToMatrix4', 'ParamObject');

(function(){
  for (var i = 0; i < 16; i++) {
    o3d.ParamObject.setUpO3DParam_(
        o3d.ParamOp16FloatsToMatrix4, "input"+i, "ParamFloat");
  }
  o3d.ParamObject.setUpO3DParam_(
      o3d.ParamOp16FloatsToMatrix4, "output", "ParamMatrix4Output");
})();

/**
 * Called by o3d.Param*Output whenever its value gets read.
 * @return {!Array.<!Array.<number>>} 4x4 array equal to
 *     [[i0,i1,i2,i3],[i4,i5,i6,i7],[i8,i9,i10,i11],[i12,i13,i14,i15]]
 */
o3d.ParamOp16FloatsToMatrix4.prototype.updateOutputs = function() {
  for (var i = 0; i < 16; i++) {
    this.last_output_value_[Math.floor(i/4)][i%4] =
        this.getParam("input"+i).value;
  }
  return this.last_output_value_;
};

/**
 * A Param operation that takes 9 floats to produce a 4-by-4 matrix.
 * The 9 floats encode a translation vector, angles of rotation around
 * the x, y, and z axes, and three scaling factors. The resulting
 * transformation scales first, then then rotates around the z-axis,
 * then the y-axis, then the x-axis, then translates.
 * @param {string} name Tne name of the parameter.
 * @param {string} className The param class name.
 * @param {number} numElements The number of Elements if the param is an array.
 * @param {string} sasClassName The sas class name if the param is an sas type.
 * @param {string} semantic The relevant semantic.
 * @constructor
 * @extends {o3d.ParamObject}
 */
o3d.TRSToMatrix4 = function() {
  o3d.ParamObject.call(this);

  this.rotateX = 0;
  this.rotateY = 0;
  this.rotateZ = 0;

  this.translateX = 0;
  this.translateY = 0;
  this.translateZ = 0;

  this.scaleX = 1;
  this.scaleY = 1;
  this.scaleZ = 1;

  this.last_output_value_ =
      [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];
};
o3d.inherit('TRSToMatrix4', 'ParamObject');

(function(){
  var proplist = ["rotateX", "rotateY", "rotateZ",
                  "translateX", "translateY", "translateZ",
                  "scaleX", "scaleY", "scaleZ"];
  for (var i = 0; i < proplist.length; i++) {
    o3d.ParamObject.setUpO3DParam_(o3d.TRSToMatrix4, proplist[i], "ParamFloat");
  }
  o3d.ParamObject.setUpO3DParam_(
      o3d.TRSToMatrix4, "output", "ParamMatrix4Output");
})();

/**
 * Called by o3d.Param*Output whenever its value gets read.
 * @return {!Array.<!Array.<number>>} Matrix4 equal to applying the operations
 *     in the order Translate * Rotate * Scale.
 */
o3d.TRSToMatrix4.prototype.updateOutputs = function () {
  var ret = this.last_output_value_;
  var rX = this.rotateX;
  var rY = this.rotateY;
  var rZ = this.rotateZ;
  var sX = this.scaleX;
  var sY = this.scaleY;
  var sZ = this.scaleZ;
  var sinX = Math.sin(rX);
  var cosX = Math.cos(rX);
  var sinY = Math.sin(rY);
  var cosY = Math.cos(rY);
  var sinZ = Math.sin(rZ);
  var cosZ = Math.cos(rZ);
  var cosZSinY = cosZ * sinY;
  var sinZSinY = sinZ * sinY;

  ret[0].splice(0, 4, cosZ * cosY * sX,
                      sinZ * cosY * sX,
                      -sinY * sX,
                      0);
  ret[1].splice(0, 4, (cosZSinY * sinX - sinZ * cosX) * sY,
                      (sinZSinY * sinX + cosZ * cosX) * sY,
                      cosY * sinX * sY,
                      0);
  ret[2].splice(0, 4, (cosZSinY * cosX + sinZ * sinX) * sZ,
                      (sinZSinY * cosX - cosZ * sinX) * sZ,
                      cosY * cosX * sZ,
                      0);
  ret[3].splice(0, 4, this.translateX,
                      this.translateY,
                      this.translateZ,
                      1);
  return ret;
};

/**
 * A Param operation that takes an input matrix and a local matrix
 * to produce an output matrix.
 * @constructor
 * @extends {o3d.ParamObject}
 */
o3d.Matrix4Composition = function() {
  o3d.ParamObject.call(this);
  this.last_output_value_ =
      [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];
};
o3d.inherit('Matrix4Composition', 'ParamObject');

o3d.ParamObject.setUpO3DParam_(
    o3d.Matrix4Composition, "inputMatrix", "ParamMatrix4");

o3d.ParamObject.setUpO3DParam_(
    o3d.Matrix4Composition, "localMatrix", "ParamMatrix4");

o3d.ParamObject.setUpO3DParam_(
    o3d.Matrix4Composition, "outputMatrix", "ParamMatrix4Output");

/**
 * Called by o3d.Param*Output whenever its value gets read.
 * @return {!Array.<!Array.<number>>} 4x4 array equal to
 *     inputMatrix * localMatrix
 */
o3d.Matrix4Composition.prototype.updateOutputs = function() {
  var input = this.getParam("inputMatrix").value;
  var local = this.getParam("localMatrix").value;
  o3d.Transform.compose(input, local, this.last_output_value_);
  return this.last_output_value_;
};


/**
 * A Param operation that takes an input matrix, a float3 axis and an angle
 * to produce an output matrix.
 * @constructor
 * @extends {o3d.ParamObject}
 */
o3d.Matrix4AxisRotation = function() {
  o3d.ParamObject.call(this);
  this.last_output_value_ =
      [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];
};
o3d.inherit('Matrix4AxisRotation', 'ParamObject');

o3d.ParamObject.setUpO3DParam_(
    o3d.Matrix4AxisRotation, "inputMatrix", "ParamMatrix4");

o3d.ParamObject.setUpO3DParam_(
    o3d.Matrix4AxisRotation, "axis", "ParamFloat3");

o3d.ParamObject.setUpO3DParam_(
    o3d.Matrix4AxisRotation, "angle", "ParamFloat");

o3d.ParamObject.setUpO3DParam_(
    o3d.Matrix4AxisRotation, "outputMatrix", "ParamMatrix4Output");

/**
 * Called by o3d.Param*Output whenever its value gets read.
 * @return {!Array.<!Array.<number>>} 4x4 array from rotating inputMatrix around
 *     axis by angle.
 */
o3d.Matrix4AxisRotation.prototype.updateOutputs = function() {
  var input = this.getParam("inputMatrix").value;
  var axis = this.getParam("axis").value;
  var angle = this.getParam("angle").value;
  o3d.Transform.axisRotateMatrix(input, axis, angle, this.last_output_value_);
  return this.last_output_value_;
};


/**
 * A Param operation that takes an input matrix and a float3 scale
 * to produce an output matrix.
 * @constructor
 * @extends {o3d.ParamObject}
 */
o3d.Matrix4Scale = function() {
  o3d.ParamObject.call(this);
  this.last_output_value_ =
      [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];
};
o3d.inherit('Matrix4Scale', 'ParamObject');

o3d.ParamObject.setUpO3DParam_(
    o3d.Matrix4Scale, "inputMatrix", "ParamMatrix4");

o3d.ParamObject.setUpO3DParam_(
    o3d.Matrix4Scale, "scale", "ParamFloat3");

o3d.ParamObject.setUpO3DParam_(
    o3d.Matrix4Scale, "outputMatrix", "ParamMatrix4Output");

/**
 * Called by o3d.Param*Output whenever its value gets read.
 * @return {!Array.<!Array.<number>>} 4x4 array from scaling inputMatrix by
 *      scale.
 */
o3d.Matrix4Scale.prototype.updateOutputs = function() {
  var m = this.getParam("inputMatrix").value;
  var ret = this.last_output_value_;
  var v = this.getParam("scale").value;

  var v0 = v[0];
  var v1 = v[1];
  var v2 = v[2];

  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];

  ret[0].splice(0, 4, v0 * m0[0], v0 * m0[1], v0 * m0[2], v0 * m0[3]);
  ret[1].splice(0, 4, v1 * m1[0], v1 * m1[1], v1 * m1[2], v1 * m1[3]);
  ret[2].splice(0, 4, v2 * m2[0], v2 * m2[1], v2 * m2[2], v2 * m2[3]);
  ret[3] = m3.slice(0);
  return ret;
};


/**
 * A Param operation that takes an input matrix, and a translation
 * to produce an output matrix.
 * @constructor
 * @extends {o3d.ParamObject}
 */
o3d.Matrix4Translation = function() {
  o3d.ParamObject.call(this);
  this.last_output_value_ =
      [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];
};
o3d.inherit('Matrix4Translation', 'ParamObject');

o3d.ParamObject.setUpO3DParam_(
    o3d.Matrix4Translation, "inputMatrix", "ParamMatrix4");

o3d.ParamObject.setUpO3DParam_(
    o3d.Matrix4Translation, "translation", "ParamFloat3");

o3d.ParamObject.setUpO3DParam_(
    o3d.Matrix4Translation, "outputMatrix", "ParamMatrix4Output");

/**
 * Called by o3d.Param*Output whenever its value gets read.
 * @return {!Array.<!Array.<number>>} 4x4 array from translating inputMatrix
 *     by translation.
 */
o3d.Matrix4Translation.prototype.updateOutputs = function() {
  var m = this.getParam("inputMatrix").value;
  var ret = this.last_output_value_;
  var v = this.getParam("translation").value;

  var v0 = v[0];
  var v1 = v[1];
  var v2 = v[2];

  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];

  ret[0] = m0.slice(0);
  ret[1] = m1.slice(0);
  ret[2] = m2.slice(0);
  ret[3].splice(0, 4, m0[0] * v0 + m1[0] * v1 + m2[0] * v2 + m3[0],
                      m0[1] * v0 + m1[1] * v1 + m2[1] * v2 + m3[1],
                      m0[2] * v0 + m1[2] * v1 + m2[2] * v2 + m3[2],
                      m0[3] * v0 + m1[3] * v1 + m2[3] * v2 + m3[3]);
  return ret;
};


/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * A Function is a class that has an Evaluate method.
 * Evaluate takes 1 input and returns 1 output.
 * @param {function(number)=} opt_func  evaluate function to use (unless this
 *     class is extended
 * @constructor
 * @extends {o3d.NamedObject}
 */
o3d.Function = function(opt_func) {
    o3d.NamedObject.call(this);
    this.func_ = opt_func;
};
o3d.inherit('Function', 'NamedObject');

/**
 * Gets an output for this function for the given input.
 * @param {number} input Input to get output at.
 * @param {object=} opt_context Cached state from the previous evaluate call.
 * @return {number} The output for the given input.
 */
o3d.Function.prototype.evaluate = function(input, opt_context) {
    this.func_.call(this, input, opt_context);
};

/**
 * A FunctionEval evaluates a Function through parameters.
 * @constructor
 * @extends {o3d.ParamObject}
 */
o3d.FunctionEval = function() {
  o3d.ParamObject.call(this);

  /**
   * Read/write input value to the function.
   * @private
   * @type {o3d.ParamFloat}
   */
  this.input_param_ = this.getParam("input");

  /**
   * ParamFunction whose value is a o3d.Function or subclass.
   * @private
   * @type {o3d.ParamFunction}
   */
  this.func_param_ = this.getParam("functionObject");

  /**
   * Read-only output value. Value is cached if input does not change.
   * @private
   * @type {o3d.ParamFloatOutput}
   */
  this.output_param_ = this.getParam("output");

  /**
   * Context value to allow faster evaluation for adjacent input values.
   * Used by the o3d.Function itself, but stored here to allow multiple
   * FunctionEval objects to share the same Function object.
   * @private
   * @type {object}
   */
  this.func_context_ = {};

  /**
   * Last input value to check if cache needs to be invalidated.
   * @private
   * @type {number}
   */
  this.last_input_value_ = null;

  /**
   * Cache of the last output value.
   * @private
   * @type {number}
   */
  this.last_output_value_ = null;
};
o3d.inherit('FunctionEval', 'ParamObject');

/**
 * Read-only output value. Value is cached if input does not change.
 * @type {number}
 */
o3d.ParamObject.setUpO3DParam_(o3d.FunctionEval, "output", "ParamFloatOutput");

/**
 * Read/write input value to the function.
 * @type {number}
 */
o3d.ParamObject.setUpO3DParam_(o3d.FunctionEval, "input", "ParamFloat");

/**
 * o3d.Function object (or subclass) with evaluate function.
 * @type {o3d.Function}
 */
o3d.ParamObject.setUpO3DParam_(o3d.FunctionEval, "functionObject",
    "ParamFunction");

/**
 * Called by o3d.Param*Output whenever its value gets read.
 * This function should be responsible for caching the last value if necessary.
 * @return {number} The result of evaluating the function.
 */
o3d.FunctionEval.prototype.updateOutputs = function() {
  var new_input_value = this.input_param_.value;
  if (this.last_input_value_ != new_input_value) {
    this.last_output_value_ =
        this.func_param_.value.evaluate(this.last_input_value_,
                                        this.func_context_);
    this.last_input_value_ = new_input_value;
  }
  return this.last_output_value_;
};
/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * Per-client list of counters/timers.
 * @constructor
 */
o3d.CounterManager = function() {
  /**
   * Map from counter type to list of counters in this client.
   * @private
   * @type {!Object}
   */
  this.counterMap_ = {};
};

/**
 * Contains the time of the last call to tick, so we know the amount to pass
 * into advanceCounters_
 * lastUpdated is initialized on the first call to tick().
 * @type {Date}
 */
o3d.CounterManager.prototype.lastUpdated = null;

/**
 * @param {string} type Which counter_type_ to advance.
 * @param {number} amount Amount to advance counters.
 */
o3d.CounterManager.prototype.advanceCounters = function(type, amount) {
  var counterArrayRef = this.counterMap_[type];
  if (!counterArrayRef)
    return;
  var length = counterArrayRef.length;
  var counterArray = [];
  for (var i = 0; i < length; i++) {
    counterArray[i] = counterArrayRef[i];
  }
  for (var i = 0; i < length; ++i) {
    if (counterArray[i].running) {
      counterArray[i].advance(amount);
    }
  }
};

/**
 * Static method to increments all global counters. RenderFrameCounter should
 * not get updated here.
 */
o3d.CounterManager.prototype.tick = function() {
  this.advanceCounters("TickCounter", 1.0);
  var now = new Date();
  var deltaSeconds = 0;
  if (this.lastUpdated != null) {
    deltaSeconds = (now - this.lastUpdated) / 1000.0;
  }
  this.lastUpdated = now;
  this.advanceCounters("SecondCounter", deltaSeconds);
};

/**
 * Updates RenderFrameCounter objects by 1.0, similarly to TickCounter.
 * As opposed to tick(), this should only be when the canvas is rendered, so
 * any canvases which are not automatically rendererd may not advance this.
 */
o3d.CounterManager.prototype.advanceRenderFrameCounters = function() {
  this.advanceCounters("RenderFrameCounter", 1.0);
};

/**
 * Register a counter, using its className to decide which type of counter.
 * @param {!o3d.Counter} counter Instance of some sub-type of o3d.Counter
 */
o3d.CounterManager.prototype.registerCounter = function(counter) {
  var type = counter.counter_type_;
  var arr = this.counterMap_[type];
  if (!arr) {
    arr = this.counterMap_[type] = [];
  }
  arr.push(counter);
};

/**
 * Unregister a counter previously registered by registerCounter.
 * @param {!o3d.Counter} counter Instance of some sub-type of o3d.Counter
 */
o3d.CounterManager.prototype.unregisterCounter = function(counter) {
  var type = counter.counter_type_;
  var arr = this.counterMap_[type];
  if (arr) {
    o3d.removeFromArray(arr, counter);
  }
};

/**
 * A Counter counts seconds, ticks or render frames depending on the type of
 * counter. You can set where it starts counting from and where it stops
 * counting at, whether or not it is running or paused and how it loops or does
 * not loop. You can also give it callbacks to call at specific count values.
 * @constructor
 * @extends {o3d.ParamObject}
 */
o3d.Counter = function() {
  o3d.ParamObject.call(this);

  /**
   * Last called callback in the forward direction (start_count > end_count)
   * @type {number}
   * @private
   */
  this.next_callback_ = -1;

  /**
   * Last called callback in the backward direction (start_count < end_count)
   * @type {number}
   * @private
   */
  this.prev_callback_ = -1;

  /**
   * Keeps track of last count in case this.count_ is bound to another param.
   * @type {number}
   * @private
   */
  this.old_count_ = 0;

  /**
   * Keeps track of last end_count to prevent double-firing of callbacks if
   * start_count is the same.
   * @type {number}
   * @private
   */
  this.last_call_callbacks_end_count_ = 0;

  /**
   * List of callbacks for this counter, in sorted order.
   * There can only be one callback per timer value.
   * @type {Array.<o3d.Counter.CallbackInfo>}
   * @private
   */
  this.callbacks_ = [];

  /**
   * Pointer to CounterManager which owns this counter, so it can be added
   * to the list of counters by setting this.running = true.
   * @type {o3d.CounterManager}
   */
  this.counter_manager_ = null;

  /**
   * Cached copy of this.getParam('running')
   * @type {o3d.ParamBoolean}
   * @private
   */
  this.running_param_ = this.createParam("running", "ParamBoolean");

  /**
   * Cached copy of this.getParam('count')
   * @type {o3d.ParamFloat}
   * @private
   */
  this.count_param_ = this.createParam("count", "ParamFloat");

  this.multiplier = 1.0;

  this.forward = true;

  this.countMode = o3d.Counter.CONTINUOUS;

  // this.running must be set in the sub class due to how o3d.inherit works.
};
o3d.inherit('Counter', 'ParamObject');

/**
 * Controls the direction of the counter. If false, Counter.callCallbacks_
 * will be called with start_count > end_count.
 * @default true
 * @type {boolean}
 */
o3d.ParamObject.setUpO3DParam_(o3d.Counter, "forward", "ParamBoolean");

/**
 * The start value for this counter.
 * @default 0
 * @type {number}
 */
o3d.ParamObject.setUpO3DParam_(o3d.Counter, "start", "ParamFloat");

/**
 * The end value for this counter.
 * @default 0
 * @type {number}
 */
o3d.ParamObject.setUpO3DParam_(o3d.Counter, "end", "ParamFloat");

/**
 * Can be used to switch the direction of the counter or control what happens
 * when end_count is reached.
 * @see o3d.Counter.CONTINUOUS
 * @see o3d.Counter.ONCE
 * @see o3d.Counter.CYCLE
 * @see o3d.Counter.OSCILLATE
 * @default o3d.Counter.CONTINUOUS
 * @type {o3d.Counter.CountMode}
 */
o3d.ParamObject.setUpO3DParam_(o3d.Counter, "countMode", "ParamInteger");

/**
 * The time multiplier for this counter.
 * @default 1
 * @type {number}
 */
o3d.ParamObject.setUpO3DParam_(o3d.Counter, "multiplier", "ParamFloat");

/**
 * Whether or not this counter is running.
 * Special setter to allow destroying a stopped Counter without leaking mem.
 * Defaults to true in SecondCounter, TickCounter and RenderFrameCounter.
 * @default false
 * @type {boolean}
 */
o3d.Counter.prototype.__defineSetter__("running",
    function(newRunning) {
      var oldRunning = this.running_param_.value;
      if (this.counter_manager_ && oldRunning != newRunning) {
        if (newRunning == false) {
          this.counter_manager_.unregisterCounter(this);
        } else {
          this.counter_manager_.registerCounter(this);
        }
      }
      this.running_param_.value = newRunning;
    }
);
o3d.Counter.prototype.__defineGetter__("running",
    function() {
      return this.running_param_.value;
    }
);

/**
 * The current count for this counter. The setter is equivalent to calling
 * setCount(value)
 * @default 0
 * @type {number}
 */
o3d.Counter.prototype.__defineSetter__("count",
    function(value) {
      this.setCount(value);
    }
);
o3d.Counter.prototype.__defineGetter__("count",
    function() {
      return this.count_param_.value;
    }
);

o3d.Counter.prototype.__defineSetter__("gl",
    function(new_gl) {
      var old_running = this.running;
      this.running = false;
      this.gl_ = new_gl;
      if (this.gl_ && this.gl_.client) {
        this.counter_manager_ = this.gl_.client.counter_manager_;
      }
      this.running = old_running;
    }
);

o3d.Counter.prototype.__defineGetter__("gl",
    function() {
      return this.gl_;
    }
);

/**
 * @type {number}
 */
o3d.Counter.CountMode = goog.typedef;

/**
 * Continiuously goes up, ignoring the value of end_count. This is the default.
 * @type {o3d.Counter.CountMode}
 */
o3d.Counter.CONTINUOUS = 0;
/**
 * Sets running=false when end is reached and stops.
 * @type {o3d.Counter.CountMode}
 */
o3d.Counter.ONCE = 1;
/**
 * Resets count to start_count whenever end is reached.
 * @type {o3d.Counter.CountMode}
 */
o3d.Counter.CYCLE = 2;
/**
 * Switches direction whenever end or start is reached.
 * @type {o3d.Counter.CountMode}
 * @see direction
 */
o3d.Counter.OSCILLATE = 3;

/**
 * The counter type is used by o3d.CounterManager to decide which array to
 * put the counter in, and how much to call advanceAmount by during each call
 * to o3d.CounterManager.tick() or advanceRenderFrameCounters().
 * "Counter" means that it will only be advanced manually.
 * @type {string}
 */
o3d.Counter.prototype.counter_type_ = "Counter";

/**
 * Resets the counter back to the start or end time depending on the forward
 * setting and also resets the Callback state.
 * Note: Reset does not change the running state of the counter.
 */
o3d.Counter.prototype.reset = function() {
  this.setCount(this.forward ? this.start : this.end);
};

/**
 * Sets the current count value for this counter as well as the resetting the
 * state of the callbacks.
 * @param {number} count The current value of the counter
 */
o3d.Counter.prototype.setCount = function(value) {
  this.count_param_.value = value;
  this.old_count_ = value;
  this.next_callback_ = -1;
  this.prev_callback_ = -1;
};


/**
 * Advances the counter the given amount. The actual amount advanced depends on
 * the forward and multiplier settings. The formula is
 * new_count = count + advance_amount * multiplier * (forward ? 1.0 : -1.0);
 *
 * Any callbacks that fall in the range between the counter's current count and
 * the amount advanced will be called. This function is normally called
 * automatically by the client if the counter is set to running = true. but you
 * can call it manually.
 *
 * @param {number} advance_amount Amount to advance count.
 */
o3d.Counter.prototype.advance = function(advance_amount) {
  var queue = [];
  var old_count = this.count_param_.value;

  // Update the count.
  if (this.count_param_.inputConnection) {
    this.callCallbacks_(this.old_count_, old_count, queue);
  } else {
    var direction = this.forward;
    var start_count = this.start;
    var end_count = this.end;
    var multiplier = this.multiplier;
    var delta = (direction ? advance_amount : -advance_amount) * multiplier;
    var period = end_count - start_count;

    var mode = this.countMode;
    if (period >= 0.0) {
      // end > start
      var new_count = old_count + delta;
      if (delta >= 0.0) {
        switch (mode) {
          case o3d.Counter.ONCE: {
            if (new_count >= end_count) {
              new_count = end_count;
              this.running = false;
            }
            break;
          }
          case o3d.Counter.CYCLE: {
            while (new_count >= end_count) {
              this.callCallbacks_(old_count, end_count, queue);
              if (period == 0.0) {
                break;
              }
              old_count = start_count;
              new_count -= period;
            }
            break;
          }
          case o3d.Counter.OSCILLATE: {
            while (delta > 0.0) {
              new_count = old_count + delta;
              if (new_count < end_count) {
                break;
              }
              this.callCallbacks_(old_count, end_count, queue);
              direction = !direction;
              var amount = end_count - old_count;
              delta -= amount;
              old_count = end_count;
              new_count = end_count;
              if (delta <= 0.0 || period == 0.0) {
                break;
              }
              new_count -= delta;
              if (new_count > start_count) {
                break;
              }
              this.callCallbacks_(old_count, start_count, queue);
              direction = !direction;
              amount = old_count - start_count;
              delta -= amount;
              old_count = start_count;
              new_count = start_count;
            }
            this.forward = direction;
            break;
          }
          case o3d.Counter.CONTINUOUS:
          default:
            break;
        }
        this.callCallbacks_(old_count, new_count, queue);
        this.count_param_.value = new_count;
      } else if (delta < 0.0) {
        switch (mode) {
          case o3d.Counter.ONCE: {
            if (new_count <= start_count) {
              new_count = start_count;
              this.running = false ;
            }
            break;
          }
          case o3d.Counter.CYCLE: {
            while (new_count <= start_count) {
              this.callCallbacks_(old_count, start_count, queue);
              if (period == 0.0) {
                break;
              }
              old_count = end_count;
              new_count += period;
            }
            break;
          }
          case o3d.Counter.OSCILLATE: {
            while (delta < 0.0) {
              new_count = old_count + delta;
              if (new_count > start_count) {
                break;
              }
              this.callCallbacks_(old_count, start_count, queue);
              direction = !direction;
              var amount = old_count - start_count;
              delta += amount;
              old_count = start_count;
              new_count = start_count;
              if (delta >= 0.0 || period == 0.0) {
                break;
              }
              new_count -= delta;
              if (new_count < end_count) {
                break;
              }
              this.callCallbacks_(old_count, end_count, queue);
              direction = !direction;
              amount = end_count - old_count;
              delta += amount;
              old_count = end_count;
              new_count = end_count;
            }
            this.forward = direction;
            break;
          }
          case o3d.Counter.CONTINUOUS:
          default:
            break;
        }
        this.callCallbacks_(old_count, new_count, queue);
        this.count_param_.value = new_count;
      }
    } else if (period < 0.0) {
      // start > end
      period = -period;
      var new_count = old_count - delta;
      if (delta > 0.0) {
        switch (mode) {
          case o3d.Counter.ONCE: {
            if (new_count <= end_count) {
              new_count = end_count;
              this.running = false;
            }
            break;
          }
          case o3d.Counter.CYCLE: {
            while (new_count <= end_count) {
              this.callCallbacks_(old_count, end_count, queue);
              old_count = start_count;
              new_count += period;
            }
            break;
          }
          case o3d.Counter.OSCILLATE: {
            while (delta > 0.0) {
              new_count = old_count - delta;
              if (new_count > end_count) {
                break;
              }
              this.callCallbacks_(old_count, end_count, queue);
              direction = !direction;
              var amount = old_count - end_count;
              delta -= amount;
              old_count = end_count;
              new_count = end_count;
              if (delta <= 0.0) {
                break;
              }
              new_count += delta;
              if (new_count < start_count) {
                break;
              }
              this.callCallbacks_(old_count, start_count, queue);
              direction = !direction;
              amount = start_count - old_count;
              delta -= amount;
              old_count = start_count;
              new_count = start_count;
            }
            this.forward = direction;
            break;
          }
          case o3d.Counter.CONTINUOUS:
          default:
            break;
        }
        this.callCallbacks_(old_count, new_count, queue);
        this.count_param_.value = new_count;
      } else if (delta < 0.0) {
        switch (mode) {
          case o3d.Counter.ONCE: {
            if (new_count >= start_count) {
              new_count = start_count;
              this.running = false;
            }
            break;
          }
          case o3d.Counter.CYCLE: {
            while (new_count >= start_count) {
              this.callCallbacks_(old_count, start_count, queue);
              old_count = end_count;
              new_count -= period;
            }
            break;
          }
          case o3d.Counter.OSCILLATE: {
            while (delta < 0.0) {
              new_count = old_count - delta;
              if (new_count < start_count) {
                break;
              }
              this.callCallbacks_(old_count, start_count, queue);
              direction = !direction;
              var amount = start_count - old_count;
              delta += amount;
              old_count = start_count;
              new_count = start_count;
              if (delta >= 0.0) {
                break;
              }
              new_count += delta;
              if (new_count > end_count) {
                break;
              }
              this.callCallbacks_(old_count, end_count, queue);
              direction = !direction;
              amount = old_count - end_count;
              delta += amount;
              old_count = end_count;
              new_count = end_count;
            }
            this.forward = direction;
            break;
          }
          case o3d.Counter.CONTINUOUS:
          default:
            break;
        }
        this.callCallbacks_(old_count, new_count, queue);
        this.count_param_.value = new_count;
      }
    }
  }
  this.old_count_ = old_count;
  for (var i = 0; i < queue.length; i++) {
    queue[i]();
  }
};

/**
 * @param {number} start_count  Starts exactly at this count.
 * @param {number} end_count  Calls callbacks up to but not including this.
 * @private
 */
o3d.Counter.prototype.callCallbacks_ = function(start_count, end_count, queue) {
  if (end_count > start_count) {
    // Going forward.
    // If next_callback is not valid, find the first possible callback.
    if (this.next_callback_ < 0 ||
        start_count != this.last_call_callbacks_end_count_) {
      this.next_callback_ = 0;
      while (this.next_callback_ != this.callbacks_.length &&
             this.callbacks_[this.next_callback_].count < start_count) {
        ++this.next_callback_;
      }
    }

    // add callbacks until we get to some callback past end_count.
    while (this.next_callback_ < this.callbacks_.length) {
      if (this.callbacks_[this.next_callback_].count > end_count) {
        break;
      }
      queue.push(this.callbacks_[this.next_callback_].callback);
      ++this.next_callback_;
    }
    this.prev_callback_ = -1;
    this.last_call_callbacks_end_count_ = end_count;
  } else if (end_count < start_count) {
    // Going backward.
    // If prev_callback is not valid, find the first possible callback.
    if (this.prev_callback_ < 0 ||
        start_count != this.last_call_callbacks_end_count_) {
      this.prev_callback_ = this.callbacks_.length - 1;
      while (this.prev_callback_ >= 0 &&
             this.callbacks_[this.prev_callback_].count > start_count) {
        --this.prev_callback_;
      }
    }

    // add callbacks until we get to some callback past end_count.
    while (this.prev_callback_ >= 0) {
      if (this.callbacks_[this.prev_callback_].count < end_count) {
        break;
      }
      queue.push(this.callbacks_[this.prev_callback_].callback);
      --this.prev_callback_;
    }

    this.next_callback_ = -1;
    this.last_call_callbacks_end_count_ = end_count;
  }
};

/**
 * Adds a callback for a given count value. Only one callback can be added to a
 * specific count value. If another callback is added with the same count value
 * the previous callback for that value will be replaced. Note: A callback at
 * start will only get called when counting backward, a callback at end will
 * only get called counting forward.
 * @param {number} count Count at which to call callback.
 * @param {function()} callback Callback to call at given count.
 */
o3d.Counter.prototype.addCallback = function(count, callback) {
  this.next_callback_ = -1;
  this.prev_callback_ = -1;
  var end = this.callbacks_.length;
  var iter = 0;
  while (iter != end) {
    var current = this.callbacks_[iter];
    if (current.count == count) {
      // Did the o3d plugin overwrite existing callbacks here?
      current.callback = callback;
      return;
    } else if (current.count > count) {
      break;
    }
    ++iter;
  }
  var rest = this.callbacks_.splice(iter, this.callbacks_.length - iter);
  this.callbacks_.push(new o3d.Counter.CallbackInfo(count, callback));
  this.callbacks_.push.apply(this.callbacks_, rest);
};

/**
 * Removes a callback for a given count value.
 * @param {number} count Count to remove callback for.
 * @return {boolean} true if there was a callback for that count, false if
 *     there was not a callback for that count.
 */
o3d.Counter.prototype.removeCallback = function(count) {
  var end = this.callbacks_.length;
  for (var iter = 0; iter != end; ++iter) {
    if (this.callbacks_[iter].count == count) {
      this.next_callback_ = -1;
      this.prev_callback_ = -1;
      this.callbacks_.splice(iter, 1);
      return true;
    }
  }
  return false;
};

/**
 * Removes all the callbacks on this counter.
 * Does not affect running state: set running=false to allow this counter to be
 * garbage collected.
 */
o3d.Counter.prototype.removeAllCallbacks = function() {
  this.callbacks_ = [];
  this.next_callback_ = -1;
  this.prev_callback_ = -1;
};

/**
 * Class to hold onto the state of a callback function at a specific count.
 * @constructor
 * @param {number} count  Count at which this callback will get called.
 * @param {function()} callback  Function to call when count is reached.
 */
o3d.Counter.CallbackInfo = function(count, callback) {
  /**
   * Count at which this callback will get called.
   * @type {number}
   */
  this.count = count;

  /**
   * Function to call when count is reached.
   * @type {function()}
   */
  this.callback = callback;

  /**
   * Prevents recursion of the same callback, in case it decides to advance
   * the counter.
   * @type {boolean}
   * @private
   */
  this.called_ = false;
};

/**
 * Run this callback without the possibility for recursion.
 */
o3d.Counter.CallbackInfo.prototype.run = function() {
  if (!this.called_) {
    this.called_ = true;
    this.callback();
    this.called_ = false;
  }
};

/**
 * Counter to count seconds. Logic is implemented in CounterManager.tick()
 * @constructor
 * @extends {o3d.Counter}
 */
o3d.SecondCounter = function() {
  o3d.Counter.call(this);
  this.running = true;
};

o3d.inherit("SecondCounter", "Counter");

o3d.SecondCounter.prototype.counter_type_ = "SecondCounter";

/**
 * Counter to count frames. Logic is implemented in
 * CounterManager.advanceRenderFrameCounters()
 * @constructor
 * @extends {o3d.Counter}
 */
o3d.RenderFrameCounter = function() {
  o3d.Counter.call(this);
  this.running = true;
};

o3d.inherit("RenderFrameCounter", "Counter");

o3d.RenderFrameCounter.prototype.counter_type_ = "RenderFrameCounter";

/**
 * Counter to count ticks. Logic is implemented in CounterManager.tick()
 * @constructor
 * @extends {o3d.Counter}
 */
o3d.TickCounter = function() {
  o3d.Counter.call(this);
  this.running = true;
};

o3d.inherit("TickCounter", "Counter");

o3d.TickCounter.prototype.counter_type_ = "TickCounter";
/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * A CurveKey prepresents a key on an Curve.
 *
 * @constructor
 * @extends {o3d.ObjectBase}
 */
o3d.CurveKey = function(owner) {
  o3d.ObjectBase.call(this);
  /**
   * The only owner of this CurveKey (cannot be shared).
   * @private
   * @type {!o3d.Curve}
   */
  this.owner_ = owner;

  /**
   * See input setter.
   * @private
   * @type {number}
   */
  this.input_ = 0;

  /**
   * See input setter.
   * @private
   * @type {number}
   */
  this.output_ = 0;
};
o3d.inherit('CurveKey', 'ObjectBase');

/**
 * Destroys this key, removing it from its owner.
 *
 */
o3d.CurveKey.prototype.destroy = function() {
  o3d.removeFromArray(this.owner_.keys, this);
  this.owner_.invalidateCache_();
};

/**
 * Computes the value within the range from this.input to next_key.input.
 * Defaults to the LinearCurveKey implementation which is continuous.
 * @param {number} offset  Offset within the key (function input - this.input)
 * @param {o3d.CurveKey} next_key The next key in the set, can not be null.
 * @return {number} return
 */
o3d.CurveKey.prototype.getOutputAtOffset = function(offset, next_key) {
  var input_span = next_key.input - this.input;
  var output_span = next_key.output - this.output;
  return this.output + offset / input_span * output_span;
};

/**
 * Input for this key. This curve key will apply until the next key's input.
 * @type {number}
 */
o3d.CurveKey.prototype.__defineGetter__("input", function() {
  return this.input_;
});
o3d.CurveKey.prototype.__defineSetter__("input", function(new_input) {
  if (new_input != this.input_) {
    this.input_ = new_input;
    this.owner_.invalidateCache_();
  }
});

/**
 * Output corresponding to input.
 * @type {number}
 */
o3d.CurveKey.prototype.__defineGetter__("output", function() {
  return this.output_;
});
o3d.CurveKey.prototype.__defineSetter__("output", function(new_output) {
  if (new_output != this.output_) {
    this.output_ = new_output;
    this.owner_.invalidateCache_();
  }
});

/**
 * An CurveKey that holds its output (is not interpolated between this key
 * and the next.)
 *
 * Other discontinuous CurveKey classes must be derived from StepCurveKey.
 *
 * @constructor
 * @extends {o3d.CurveKey}
 */
o3d.StepCurveKey = function(owner) {
  o3d.CurveKey.call(this, owner);
  owner.num_step_keys_++;
};
o3d.inherit('StepCurveKey', 'CurveKey');

/**
 * Simple discontinuous implementation.
 * @param {number} offset  Ignored: constant output within a StepCurveKey
 * @param {o3d.CurveKey} next_key Ignored: discontinuous
 * @return {number} output
 */
o3d.StepCurveKey.prototype.getOutputAtOffset = function(offset, next_key) {
  return this.output;
};

/**
 * Specialized destroy method to update the num_step_keys_ of the owner_.
 */
o3d.StepCurveKey.prototype.destroy = function() {
  o3d.CurveKey.prototype.destroy.call(this);
  this.owner_.num_step_keys_--;
};

/**
 * An CurveKey that linearly interpolates between this key and the next key.
 *
 * @constructor
 * @extends {o3d.CurveKey}
 */
o3d.LinearCurveKey = function(owner) {
  o3d.CurveKey.call(this, owner);
};
o3d.inherit('LinearCurveKey', 'CurveKey');

/**
 * An CurveKey that uses a bezier curve for interpolation between this key
 * and the next.
 *
 * @constructor
 * @extends {o3d.CurveKey}
 */
o3d.BezierCurveKey = function(owner) {
  o3d.CurveKey.call(this, owner);

  /**
   * See input setter.
   * @private
   * @type {Array.<number>}
   */
  this.in_tangent_ = [0, 0];

  /**
   * See input setter.
   * @private
   * @type {Array.<number>}
   */
  this.out_tangent_ = [0, 0];
};
o3d.inherit('BezierCurveKey', 'CurveKey');

/**
 * Tangent for values approaching this key. Do not set elements in the array
 * directly as that will prevent calling invalidateCache_.
 * @type {Array.<number>}
 */
o3d.BezierCurveKey.prototype.__defineGetter__("inTangent", function() {
  return this.in_tangent_;
});
o3d.BezierCurveKey.prototype.__defineSetter__("inTangent", function(new_t) {
  if (new_t != this.in_tangent_) {
    this.in_tangent_ = new_t;
    this.owner_.invalidateCache_();
  }
});

/**
 * Tangent for values approaching the next key. Do not set elements in the array
 * directly as that will prevent calling invalidateCache_.
 * @type {Array.<number>}
 */
o3d.BezierCurveKey.prototype.__defineGetter__("outTangent", function() {
  return this.out_tangent_;
});
o3d.BezierCurveKey.prototype.__defineSetter__("outTangent", function(new_t) {
  if (new_t != this.out_tangent_) {
    this.out_tangent_ = new_t;
    this.owner_.invalidateCache_();
  }
});

/**
 * Uses iterative method to accurately pin-point the 't' of the Bezier
 * equation that corresponds to the current time.
 * @param {number} control_point_0_x  this.input
 * @param {number} control_point_1_x  this.outTangent
 * @param {number} control_point_2_x  next.inTangent
 * @param {number} control_point_3_x  next.input
 * @param {number} input Absolute input value relative to span
 * @param {number} initial_guess Starting point assuming mostly linear.
 * @private
 */
o3d.BezierCurveKey.findT_ = function(control_point_0_x,
                                    control_point_1_x,
                                    control_point_2_x,
                                    control_point_3_x,
                                    input,
                                    initial_guess) {
  var local_tolerance = 0.001;
  var high_t = 1.0;
  var low_t = 0.0;

  // TODO: Optimize here, start with a more intuitive value than 0.5
  //     (comment left over from original code)
  var mid_t = 0.5;
  if (initial_guess <= 0.1) {
    mid_t = 0.1;  // clamp to 10% or 90%, because if miss, the cost is
                   // too high.
  } else if (initial_guess >= 0.9) {
    mid_t = 0.9;
  } else {
    mid_t = initial_guess;
  }
  var once = true;
  while ((high_t-low_t) > local_tolerance) {
    if (once) {
      once = false;
    } else {
      mid_t = (high_t - low_t) / 2.0 + low_t;
    }
    var ti = 1.0 - mid_t;  // (1 - t)
    var calculated_time = control_point_0_x * ti * ti * ti +
                            3 * control_point_1_x * mid_t * ti * ti +
                            3 * control_point_2_x * mid_t * mid_t * ti +
                            control_point_3_x * mid_t * mid_t * mid_t;
    if (Math.abs(calculated_time - input) <= local_tolerance) {
      break;  // If we 'fall' very close, we like it and break.
    }
    if (calculated_time > input) {
      high_t = mid_t;
    } else {
      low_t = mid_t;
    }
  }
  return mid_t;
};

/**
 * Computes the value of the Bezier curve within the range from this.input
 * to next_key.input.
 * @param {number} offset  Offset within the key (function input - this.input)
 * @param {o3d.CurveKey} next_key The next key in the set, can not be null.
 * @return {number} return
 */
o3d.BezierCurveKey.prototype.getOutputAtOffset = function(offset, next_key) {
  var input_span = next_key.input - this.input;
  var output_span = next_key.output - this.output;
  var in_tangent;

  // We check bezier first because it's the most likely match for another
  // bezier key.
  if (next_key.inTangent) {
    in_tangent = next_key.inTangent;
  } else {
    in_tangent = [next_key.input - input_span / 3.0,
                  next_key.output - output_span / 3.0];
  }

  // Do a bezier calculation.
  var t = offset / input_span;
  t = o3d.BezierCurveKey.findT_(this.input,
                               this.outTangent[0],
                               in_tangent[0],
                               next_key.input,
                               this.input + offset,
                               t);
  var b = this.outTangent[1];
  var c = in_tangent[1];
  var ti = 1.0 - t;
  var br = 3.0;
  var cr = 3.0;
  return this.output * ti * ti * ti + br * b * ti * ti * t +
      cr * c * ti * t * t + next_key.output * t * t * t;
};

/**
 * A Curve stores a bunch of CurveKeys and given a value
 * representing an input point on a curve returns the output of the curve for
 * that input. Curve is data only. It is used by 1 or more
 * FunctionEval objects or by direct use from javascript.
 *
 * @constructor
 * @extends {o3d.Function}
 */
o3d.Curve = function() {
  o3d.Function.call(this, this.evaluate);

  /**
   * The behavior of the curve before the first key.
   * @see o3d.Curve.CONSTANT
   * @see o3d.Curve.LINEAR
   * @see o3d.Curve.CYCLE
   * @see o3d.Curve.CYCLE_RELATIVE
   * @see o3d.Curve.OSCILLATE
   * @type {o3d.Curve.Infinity}
   * @default CONSTANT
   */
  this.preInfinity = o3d.Curve.CONSTANT;

  /**
   * The behavior of the curve before the first key.
   * @see o3d.Curve.CONSTANT
   * @see o3d.Curve.LINEAR
   * @see o3d.Curve.CYCLE
   * @see o3d.Curve.CYCLE_RELATIVE
   * @see o3d.Curve.OSCILLATE
   * @type {o3d.Curve.Infinity}
   * @default CONSTANT
   */
  this.postInfinity = o3d.Curve.CONSTANT;

  /**
   * Whether or not a cache is used to speed up evaluation of this Curve.
   */
  this.useCache = true;

  /**
   * Data for sampleRate setter.
   * @type {number}
   * @private
   */
  this.sample_rate_ = o3d.Curve.kDefaultSampleRate;

  /**
   * The keys for this curve.
   *
   * This property is read-only.
   * @type {!Array.<!o3d.CurveKey>}
   */
  this.keys = [];

  /**
   * Keep track if a new key has been added which hasn't been sorted.
   * @type {boolean}
   * @private
   */
  this.sorted_ = true;

  /**
   * True if the curve needs to checks for discontinuity errors before the next
   * evauluation.
   * @see updateCurveInfo_
   * @type {boolean}
   * @private
   */
  this.check_discontinuity_ = false;

  /**
   * Keep track if any discontinuous (steps or gaps) keys are in the mix.
   * Call isDiscontinuous to access this value, which updates it if necessary.
   * @type {boolean}
   * @private
   */
  this.discontinuous_= false;

  /**
   * @type {number} Number of step keys--used to speed up updateCurveInfo_
   *     discontinuity check if it's non-zero.
   * @private
   */
  this.num_step_keys_ = 0;

};

o3d.inherit('Curve', 'Function');

/**
 * Constant representing the fastest possible sample period. More samples take
 * more computation initially and more memroy.
 */
o3d.Curve.kMinimumSampleRate = 1.0 / 240.0;

/**
 * By default, sample 30 times per curve key.
 */
o3d.Curve.kDefaultSampleRate = 1.0 / 30.0;

/**
 * Gets the sample rate for the cache.  By default Animation data is
 * cached so that using the animation is fast. To do this the keys that
 * represent the animation are sampled. The higher the frequency of the
 * samples the closer the cache will match the actual keys.
 * The default is 1/30 (30hz). You can set it anywhere from 1/240th (240hz) to
 * any larger value. Note: Setting the sample rate has the side effect of
 * invalidating the cache thereby causing it to get rebuilt.
 * Must be 1/240 or greater. Default = 1/30.
 *
 * @type {number}
 */
o3d.Curve.prototype.__defineGetter__("sampleRate", function() {
  return this.sample_rate_;
});

o3d.Curve.prototype.__defineSetter__("sampleRate", function(rate) {
  if (rate < o3d.Curve.kMinimumSampleRate) {
    rate = o3d.Curve.kMinimumSampleRate;
    this.gl.client.error_callback(
        "attempt to set sample rate to " + rate +
        " which is lower than the minimum of " + o3d.Curve.kMinimumSampleRate);
  } else if (rate != this.sample_rate_) {
    this.sample_rate_ = rate;
    this.invalidateCache_();
  }
});

/**
 * @type {number}
 */
o3d.Curve.Infinity = goog.typedef;

/**
 * Uses the output value of the first or last animation key.
 * @type {o3d.Curve.Infinity}
 */
o3d.Curve.CONSTANT = 0;

/**
 * Takes the distance between the closest animation key input value and the
 * evaluation time. Multiplies this distance against the instant slope at the
 * closest animation key and offsets the result with the closest animation key
 * output value.
 * @type {o3d.Curve.Infinity}
 */
o3d.Curve.LINEAR = 1;

/**
 * Cycles over the first and last keys using:
 *       input = (input - first) % (last - first) + first;
 * Note that in CYCLE mode you can never get the end output because a cycle
 * goes from start to end exclusive of end.
 * @type {o3d.Curve.Infinity}
 */
o3d.Curve.CYCLE = 2;

/**
 * Same as cycle except the offset of the entire cycle is added to each
 * consecutive cycle.
 * @type {o3d.Curve.Infinity}
 */
o3d.Curve.CYCLE_RELATIVE = 3;

/**
 * Ping Pongs between the first and last keys.
 * @type {o3d.Curve.Infinity}
 */
o3d.Curve.OSCILLATE = 4;

/**
 * Deserializes from the curve data given a RawData object.
 *
 * @param {!o3d.RawData} rawData contains curve data
 * @param {number} opt_offset is a byte offset from the start of raw_data
 * @param {number} opt_length is the byte length of the data to set
 * @return {boolean} True if operation was successful.
 *
 */
o3d.Curve.prototype.set = function(rawData, opt_offset, opt_length) {
  o3d.notImplemented();
};

/**
 * Creates a new key for this curve.
 * @param {string} keyType name of key class to create. Valid type names are:
 *     <li> 'StepCurveKey',
 *     <li> 'LinearCurveKey',
 *     <li> 'BezierCurveKey',
 * @return {!o3d.CurveKey} The created key.
 *
 */
o3d.Curve.prototype.createKey = function(keyType) {
  var newKey = new (o3d[keyType]) (this);
  this.keys.push(newKey);
  return newKey;
};

/**
 * Adds 1 or more LinearKeys to this Curve.
 *
 * Example:
 * <pre>
 * // Creates 2 keys.
 * // 1 key at 0 with an output of 10
 * // 1 key at 20 with an output of 30
 * curve.addLinearKeys([0,10,20,30]);
 * </pre>.
 *
 * @param {!Array.<number>} values Array of input, output pairs.
 *     Length must be a multiple of 2
 */
o3d.Curve.prototype.addLinearKeys = function(values) {
  var kNumLinearKeyValues = 2;
  if (values.length % kNumLinearKeyValues != 0) {
    this.gl.client.error_callback(
        "addLinearKeys: expected multiple of 2 values got "+values.size());
    return;
  }
  for (var i = 0; i < values.length; i += kNumLinearKeyValues) {
    var newKey = this.createKey("LinearCurveKey");
    newKey.input = values[i];
    newKey.output = values[i+1];
  }
  this.sorted_ = false;
};

/**
 * Adds 1 or more StepKeys to this Curve.
 *
 * Example:
 * <pre>
 * // Creates 2 keys.
 * // 1 key at 0 with an output of 10
 * // 1 key at 20 with an output of 30
 * curve.addStepKeys([0,10,20,30]);
 * </pre>.
 *
 * @param {!Array.<number>} values Array of input, output pairs.
 *     Length must be a multiple of 2
 */
o3d.Curve.prototype.addStepKeys = function(values) {
  var kNumStepKeyValues = 2;
  if (values.length % kNumStepKeyValues != 0) {
    this.gl.client.error_callback(
        "addStepKeys: expected multiple of 2 values got "+values.size());
    return;
  }
  for (var i = 0; i < values.length; i += kNumStepKeyValues) {
    var newKey = this.createKey("StepCurveKey");
    newKey.input = values[i];
    newKey.output = values[i+1];
  }
  this.sorted_ = false;
};

/**
 * Adds 1 or more BezierKeys to this Curve.
 *
 * Example:
 * <pre>
 * // Creates 2 keys.
 * // 1 key at 0 with an output of 10, in tangent of 1,9, out tangent 9,0.5
 * // 1 key at 20 with an output of 30, in tangent of 30, 3, out tangent 4, 28
 * curve.addBezierKeys([0,10,1,9,9,0.5,2,30,3,4,28]);
 * </pre>.
 *
 * @param {!Array.<number>} values Array of tuples of the form (input, output,
 *     inTangent[0], inTangent[1], outTangent[0], outTangent[1]).
 *     Length must be a multiple of 6.
 */
o3d.Curve.prototype.addBezierKeys = function(values) {
  var kNumBezierKeyValues = 6;
  if (values.length % kNumBezierKeyValues != 0) {
    this.gl.client.error_callback(
        "addBezierKeys: expected multiple of 6 values got "+values.size());
    return;
  }
  for (var i = 0; i < values.length; i += kNumBezierKeyValues) {
    var newKey = this.createKey("BezierCurveKey");
    newKey.input = values[i];
    newKey.output = values[i+1];
    newKey.inTangent[0] = values[i+2];
    newKey.inTangent[1] = values[i+3];
    newKey.outTangent[0] = values[i+4];
    newKey.outTangent[1] = values[i+5];
  }
  this.sorted_ = false;
};

/**
 * Force updating the cache or checking discontinuity.
 * @private
 */
o3d.Curve.prototype.invalidateCache_ = function() {
  this.check_valid_ = false;
  this.check_discontinuity_ = true;
};

/**
 * Returns whether or not the curve is discontinuous. A discontinuous curve
 * takes more time to evaluate.
 * @return {boolean} True if the curve is discontinuous.
 */
o3d.Curve.prototype.isDiscontinuous = function() {
  this.updateCurveInfo_();
  return this.discontinuous_;
};

/**
 * Comparator to allow sorting by keys by their input value.
 * @param {o3d.CurveKey} a First input to compare.
 * @param {o3d.CurveKey} b Second input to compare.
 * @return {number} positive, zero, or negative (see Array.sort)
 */
o3d.Curve.compareInputs_ = function(a, b) {
  return a.input - b.input;
};

/**
 * Sorts keys (if sorted_ is false) and updates discontinuous_
 * (if check_discontinuity_ is true).
 * Called automatically when necessary in evaluate and isDiscontinuous.
 * @private
 */
o3d.Curve.prototype.updateCurveInfo_ = function() {
  if (!this.sorted_) {
    // resort keys
    this.keys.sort(o3d.Curve.compareInputs_);
    this.sorted_ = true;
    this.invalidateCache_();
  }
  if (this.check_discontinuity_) {
    // Mark the curve as discontinuous if any 2 keys share the same input and
    // if their outputs are different.
    this.check_discontinuity_ = false;
    var keys_size = this.keys.length;
    this.discontinuous_ = (this.num_step_keys_ > 0 &&
                           this.num_step_keys_ != keys_size);
    if (!this.discontinuous_ && keys_size > 1) {
      for (var ii = 0; ii < keys_size - 1; ++ii) {
        if (this.keys[ii].input == this.keys[ii + 1].input &&
            this.keys[ii].output != this.keys[ii + 1].output) {
          this.discontinuous_ = true;
          break;
        }
      }
    }
  }
};

/**
 * @param {number} input  Guaranteed to be between the first and last key.
 * @param {object} context  Generic cache to speed up adjacent computations.
 * @return {number} Final output value
 * @private
 */
o3d.Curve.prototype.getOutputInSpan_ = function(input, context) {
  var keys = this.keys;
  var keys_size = keys.length;
  if (input < keys[0].input) {
    this.gl.client.error_callback(
      "Curve.getOutputInSpan_: input is lower than any key");
    return 0;
  }

  if (input >= keys[keys_size-1].input) {
    return keys[keys_size-1].output;
  }

  // use the keys directly.
  var start = 0;
  var end = keys_size;
  var key_index;
  var found = false;

  var kKeysToSearch = 3;

  // See if the context already has a index to the correct key.
  if (context) {
    key_index = context.curve_last_key_index_;
    // is that index in range.
    if (key_index < end - 1) {
      // Are we between these keys.
      if (keys[key_index].input <= input &&
          keys[key_index + 1].input > input) {
        // Yes!
        found = true;
      } else {
        // No, so check which way we need to go.
        if (input > keys[key_index].input) {
          // search forward a few keys. If it's not within a few keys give up.
          var check_end = key_index + kKeysToSearch;
          if (check_end > end) {
            check_end = end;
          }
          for (++key_index; key_index < check_end; ++key_index) {
            if (keys[key_index].input <= input &&
                keys[key_index + 1].input > input) {
              // Yes!
              found = true;
              break;
            }
          }
        } else if (key_index > 0) {
          // search backward a few keys. If it's not within a few keys give up.
          var check_end = key_index - kKeysToSearch;
          if (check_end < 0) {
            check_end = 0;
          }
          for (--key_index; key_index >= check_end; --key_index) {
            if (keys[key_index].input <= input &&
                keys[key_index + 1].input > input) {
              // Yes!
              found = true;
              break;
            }
          }
        }
      }
    }
  }

  if (!found) {
    // TODO: If we assume the most common case is sampled keys and
    // constant intervals we can make a quick guess where that key is.

    // Find the current the keys that cover our input.
    while (start <= end) {
      var mid = Math.floor((start + end)/2);
      if (input > keys[mid].input) {
        start = mid + 1;
      } else {
        if (mid == 0) {
          break;
        }
        end = mid - 1;
      }
    }

    end = keys_size;
    while (start < end) {
      if (keys[start].input > input) {
        break;
      }
      ++start;
    }
    if (start <= 0 || start >= end) {
          this.gl.client.error_callback(
            "Curve.getOutputInSpan_: start is outside range.");
    }

    key_index = start - 1;
  }

  var key = keys[key_index];
  if (context) {
    context.curve_last_key_index_ = key_index;
  }
  if (key_index+1 >= keys_size || !keys[key_index+1]) {
    this.gl.client.error_callback(
        "Curve.getOutputInSpan_: next key is null: index is "+key_index+
        "; size is "+keys_size);
    return key.output;
  } else {
    return key.getOutputAtOffset(input - key.input, keys[key_index+1]);
  }
};

/**
 * Evaluates a point on this bezier curve corresponding to input.
 *
 * @param {number} input Input value to evaluate.
 * @param {number} context Context of the last evaluation.
 * @return {number} output value
 */
o3d.Curve.prototype.evaluate = function(input, context) {
  var keys = this.keys;
  var keys_size = keys.length;

  if (keys_size == 0) {
    return 0.0;
  }

  if (keys_size == 1) {
    return keys[0].output;
  }

  this.updateCurveInfo_();

  var start_input = keys[0].input;
  var end_input = keys[keys_size-1].input;
  var input_span = end_input - start_input;
  var start_output = keys[0].output;
  var end_output = keys[keys_size-1].output;
  var output_delta = end_output - start_output;

  var kEpsilon = 0.00001;

  var output_offset = 0.0;
  // check for pre-infinity
  if (input < start_input) {
    if (input_span <= 0.0) {
      return start_output;
    }
    var pre_infinity_offset = start_input - input;
    switch (this.preInfinity) {
      case o3d.Curve.CONSTANT:
        return start_output;
      case o3d.Curve.LINEAR: {
        var second_key = keys[1];
        var input_delta = second_key.input - start_input;
        if (input_delta > kEpsilon) {
          return start_output - pre_infinity_offset *
              (second_key.output - start_output) / input_delta;
        } else {
          return start_output;
        }
      }
      case o3d.Curve.CYCLE: {
        var cycle_count = Math.ceil(pre_infinity_offset / input_span);
        input += cycle_count * input_span;
        input = start_input + (input - start_input) % input_span;
        break;
      }
      case o3d.Curve.CYCLE_RELATIVE: {
        var cycle_count = Math.ceil(pre_infinity_offset / input_span);
        input += cycle_count * input_span;
        input = start_input + (input - start_input) % input_span;
        output_offset -= cycle_count * output_delta;
        break;
      }
      case o3d.Curve.OSCILLATE: {
        var cycle_count = Math.ceil(pre_infinity_offset / (2.0 * input_span));
        input += cycle_count * 2.0 * input_span;
        input = end_input - Math.abs(input - end_input);
        break;
      }
      default:
        this.gl.client.error_callback(
            "Curve: invalid value "+this.preInfinity+"for pre-infinity");
        return start_output;
    }
  } else if (input >= end_input) {
    // check for post-infinity
    if (input_span <= 0.0) {
      return end_output;
    }
    var post_infinity_offset = input - end_input;
    switch (this.postInfinity) {
      case o3d.Curve.CONSTANT:
        return end_output;
      case o3d.Curve.LINEAR: {
        var next_to_last_key = keys[keys_size - 2];
        var input_delta = end_input - next_to_last_key.input;
        if (input_delta > kEpsilon) {
          return end_output + post_infinity_offset *
              (end_output - next_to_last_key.output) /
              input_delta;
        } else {
          return end_output;
        }
      }
      case o3d.Curve.CYCLE: {
        var cycle_count = Math.ceil(post_infinity_offset / input_span);
        input -= cycle_count * input_span;
        input = start_input + (input - start_input) % input_span;
        break;
      }
      case o3d.Curve.CYCLE_RELATIVE: {
        var cycle_count = Math.floor((input - start_input) / input_span);
        input -= cycle_count * input_span;
        input = start_input + (input - start_input) % input_span;
        output_offset += cycle_count * output_delta;
        break;
      }
      case o3d.Curve.OSCILLATE: {
        var cycle_count = Math.ceil(post_infinity_offset / (2.0 *
                                                          input_span));
        input -= cycle_count * 2.0 * input_span;
        input = start_input + Math.abs(input - start_input);
        break;
      }
      default:
        this.gl.client.error_callback(
            "Curve.invalid value "+this.postInfinity+"for post-infinity");
        return end_output;
    }
  }

  // At this point input should be between start_input and end_input
  // inclusive.

  // If we are at end_input then just return end_output since we can't
  // interpolate end_input to anything past it.
  if (input >= end_input) {
    return end_output + output_offset;
  }

  // TODO(pathorn): Implement curve cache in javascript.
  // See 'void Curve::CreateCache' in o3d/core/cross/curve.cc

  return this.getOutputInSpan_(input, context) + output_offset;
};
/*
 * Copyright 2010, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * A Skin holds an array of matrix indices and influences for vertices in a
 * skin. A Skin is data only and can be used by one or more SkinEvals to
 * implement skinning.
 *
 * @constructor
 * @extends {o3d.NamedObject}
 */
o3d.Skin = function() {
  o3d.NamedObject.call(this);

  /**
   * Set of influences contained in this skin.
   * @type {!Array<!Array<number>>}
   */
  this.influences = [];

  /**
   * The array of inverse bone matrices (Array<matrix4>)
   *
   * @type {!Array<!Array<!Array<number>>>}
   */
  this.inverseBindPoseMatrices = [];

  /**
   * Whether the getHighestMatrixIndex and getHighestInfluences are up-to-date.
   *
   * @type {boolean}
   * @private
   */
  this.info_valid_ = false;

  /**
   * Cache of getHighestMatrixIndex().
   *
   * @type {number}
   * @private
   */
  this.highest_matrix_index_ = 0;

  /**
   * Cache of getHighestInfluences().
   *
   * @type {number}
   * @private
   */
  this.highest_influences_ = 0;

  /**
   * True if the list of weights and matrix index streams could be downloaded.
   *
   * @type {boolean}
   * @private
   */
  this.supports_vertex_shader_ = false;

  /**
   * Whether the WEIGHTS and MATRIX_INDICES buffers are up-to-date.
   *
   * @type {boolean}
   * @private
   */
  this.buffers_valid_ = false;

  /**
   * Vertex buffer containing indices and weights for this skin.
   *
   * @type {VertexBuffer}
   */
  this.vertex_buffer_ = null;

  /**
   * Buffer in which to store vertex weights.
   * weights_field_ will be passed into the WEIGHTS stream.
   *
   * @type {o3d.Field}
   * @private
   */
  this.weights_field_ = null;

  /**
   * Buffer in which to store list of matrix_indices per vertex.
   * matrix_indices_field_ will be passed into the MATRIX_INDICES stream.
   *
   * @type {o3d.Field}
   * @private
   */
  this.matrix_indices_field_ = null;
};
o3d.inherit('Skin', 'NamedObject');

/**
 * Updates the weights and indices vertex buffers attached to this skin, only
 * if they have been invalidated by some param change.
 * Also checks if we can support skinning based on the number of bones we need
 * in this skin.
 * @private
 * @return {boolean} true if our mesh is small enough to enable skin shader.
 */
o3d.Skin.prototype.updateVertexShader = function() {
  if (!this.buffers_valid_) {
    var numcomp = 4;
    var numvert = this.influences.length;
    if (!this.weights_field_ && !this.matrix_indices_field_) {
      var vertex_buffer = new o3d.VertexBuffer;
      vertex_buffer.gl = this.gl;
      this.weights_field_ =
          vertex_buffer.createField("FloatField", numcomp);
      this.matrix_indices_field_ =
          vertex_buffer.createField("FloatField", numcomp);
      vertex_buffer.allocateElements(numvert);
    }
    var ii, jj;
    var weights_field = this.weights_field_;
    var indices_field = this.matrix_indices_field_;
    var highest_influences = this.getHighestInfluences();

    this.buffers_valid_ = true;
    weights_field.buffer.lock();
    indices_field.buffer.lock();
    var max_num_bones = o3d.SkinEval.getMaxNumBones(this);
    this.supports_vertex_shader_ = (highest_influences <= numcomp) &&
        (this.inverseBindPoseMatrices.length <= max_num_bones);
    if (this.supports_vertex_shader_) {
      // NOTE: If you make these Array's instead, you must initialize to 0.
      var weights_arr = new Float32Array(numvert * numcomp);
      var indices_arr = new Float32Array(numvert * numcomp);
      // Float32rray is initialized to 0 by default.
      for (ii = 0; ii < numvert; ++ii) {
        var influence = this.influences[ii];
        for (jj = 0; jj < influence.length && jj < numcomp * 2; jj += 2) {
          indices_arr[ii * numcomp + jj / 2] = influence[jj];
          weights_arr[ii * numcomp + jj / 2] = influence[jj + 1];
        }
      }
      weights_field.setAt(0, weights_arr);
      indices_field.setAt(0, indices_arr);
    }
    // Otherwise, weights will be filled with 0's by default.
    weights_field.buffer.unlock();
    indices_field.buffer.unlock();
  }
  return this.supports_vertex_shader_;
};

/**
 * Sets the influences for an individual vertex.
 *
 * @param {number} vertex_index The index of the vertex to set influences for.
 * @param {!Array<number>} influences An array of pairs of numbers where
 *     the first number is the index of the matrix to influence the vertex
 *     and the second number is the amount of influence where:
 *     0 = no influence and 1 = 100% influence.
 */
o3d.Skin.prototype.setVertexInfluences = function(
    vertex_index, influences) {
  if (influences.length % 2 != 0) {
    this.gl.client.error_callback("odd number of values passed into" +
        "SetVertexInfluence. Even number required as they are pairs.");
    return;
  }
  this.influences[vertex_index] = influences;
  this.info_valid_ = false;
  this.buffers_valid_ = false;
};

/**
 * Gets the influences for an individual vertex.
 *
 * @param {number} vertex_index The index of the vertex to get influences from.
 * @return {!Array<number>} An array of pairs of numbers where the first number
 *     of each pair is the index of the matrix that influence this vertex and
 *     the second number is the amount of influence where:
 *     0 = no influence and 1 = 100% influence.
 */
o3d.Skin.prototype.getVertexInfluences = function(vertex_index) {
  return this.influences[vertex_index] || [];
};

/**
 * Update the highest influences and highest matrix index.
 * @private
 */
o3d.Skin.prototype.updateInfo_ = function() {
  if (!this.info_valid_) {
    this.info_valid_ = true;
    this.highest_matrix_index_ = 0;
    this.highest_influences_ = 0;
    for (var ii = 0; ii < this.influences.length; ++ii) {
      var influences = this.influences[ii];
      var len = influences.length;
      if (len > this.highest_influences_) {
        this.highest_influences_ = len;
      }
      // Influences array is in pairs: even are vertices, odd are weights
      for (var jj = 0; jj < influences.length; jj += 2) {
        if (influences[jj] > this.highest_matrix_index_) {
          this.highest_matrix_index_ = influences[jj];
        }
      }
    }
    // this.highest_influences_ should be the number of pairs.
    if (this.highest_influences_ % 2) {
      this.gl.client.error_callback(
          "Skin.updateInfo: Influences should not have odd length ");
    }
    this.highest_influences_ = Math.floor(this.highest_influences_/2);
  }
};

/**
 * Gets the highest matrix index referenced by the influences.
 *
 * @return {number} The highest matrix index referenced by the influences.
 * @private
 */
o3d.Skin.prototype.getHighestMatrixIndex = function() {
  this.updateInfo_();
  return this.highest_matrix_index_;
};

/**
 * Gets the highest number of influences on any vertex.
 *
 * @return {number} The highest number of influences on any vertex.
 * @private
 */
o3d.Skin.prototype.getHighestInfluences = function() {
  this.updateInfo_();
  return this.highest_influences_;
};

/**
 * Sets the inverse bind pose matrix for a particular joint/bone/transform.
 *
 * @param {number} index Index of bone/joint/transform.
 * @param {!Array<!Array<number>>} matrix Inverse bind pose matrix for this
 *     joint.
 */
o3d.Skin.prototype.setInverseBindPoseMatrix = function(index, matrix) {
  this.inverseBindPoseMatrices[index] = matrix;
};

/**
 * Deserializes from the skin data given a RawData object.
 *
 * @param {!o3d.RawData} rawData contains skin data
 * @param {number} opt_offset is a byte offset from the start of raw_data
 * @param {number} opt_length is the byte length of the data to set
 * @return {boolean} True if operation was successful.
 *
 */
o3d.Skin.prototype.set = function(rawData, opt_offset, opt_length) {
  o3d.notImplemented();
};



/**
 * A SkinEval is a VertexSource that takes its Streams, a ParamArray of 4-by-4
 * matrices and a Skin and skins the vertices in its streams storing the results
 * in bound output streams.
 *
 * Note: Extends StreamBank, which keeps track of storing vertexStreams objects.
 * The C++ Plugin had this inherit from VertexSource, but reading through the
 * code, I can't find any good reason why.
 *
 * @constructor
 * @extends {o3d.StreamBank}
 */
o3d.SkinEval = function() {
  o3d.StreamBank.call(this);

  /**
   * The base matrix to subtract from the matrices before skinning.
   * @type {!Array<!Array<number>>}
   */
  this.base = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];

  /**
   * Temporary storage for matrix ops.
   * @type {!Array<!Array<number>>}
   * @private
   */
  this.temp_matrix_ = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];

  /**
   * Array of matrices representing each bone.
   * @type {!Array<!Array<!Array<number>>>}
   * @private
   */
  this.bones_ = [];

  /**
   * Float32 array containing all matrices in 3x4 format.
   * @type {Float32Array}
   * @private
   */
  this.bone_array_ = null;

  /**
   * Cache of StreamInfo objects for input values. Saved to avoid reallocating.
   * @type {!Array<o3d.SkinEval.StreamInfo>}
   * @private
   */
  this.input_stream_infos_ = [];

  /**
   * Cache of StreamInfo objects for output values. Saved to avoid reallocating.
   * @type {!Array<o3d.SkinEval.StreamInfo>}
   * @private
   */
  this.output_stream_infos_ = [];

  /**
   * The base matrix to subtract from the matrices before skinning.
   *
   * @type {ParamArray}
   * @private
   */
  this.createParam("boneToWorld3x4", "ParamParamArrayOutput");

  this.usingSkinShader = 0.0;
};
o3d.inherit('SkinEval', 'StreamBank');

/**
 * The base matrix to subtract from the matrices before skinning.
 *
 * @type {!Array<!Array<number>>}
 */
o3d.ParamObject.setUpO3DParam_(o3d.SkinEval, "base", "ParamMatrix4");

/**
 * Non-zero if we are using a shader for skinning.
 *
 * Holds state of whether the boneToWorld3x4 uniform is being used. If its
 * value has been read, copy over the original vertex buffers, else we
 * assume the shader does not support skinning.
 *
 * Do not write to this value. Set disableShader to true instead.
 *
 * @type {number}
 * @see disableShader
 */
o3d.ParamObject.setUpO3DParam_(o3d.SkinEval, "usingSkinShader", "ParamFloat");

/**
 * Set this value to true to force skin not to use a shader.
 *
 * @type {boolean}
 * @default false
 */
o3d.ParamObject.setUpO3DParam_(o3d.SkinEval, "disableShader", "ParamBoolean");

/**
 * The Skin to use for skinning.
 * @type {Skin}
 */
o3d.ParamObject.setUpO3DParam_(o3d.SkinEval, "skin", "ParamSkin");

/**
 * The array of matrices to use for skinning.
 * @type {ParamArray}
 */
o3d.ParamObject.setUpO3DParam_(o3d.SkinEval, "matrices", "ParamArray");

/**
 * Skins use 3 vec4's per matrix, since the last row is redundant.
 * If we don't know, 32 is safe minimum value required by standard = (128-32)/3.
 * @param {!o3d.NamedObject} obj Some object with access to gl.
 * @return {number} Maximum number of bones allowed for shader-based skinning.
 */
o3d.SkinEval.getMaxNumBones = function(obj) {
  // Quote from spec:
  // GL_MAX_VERTEX_UNIFORM_VECTORS
  // params returns one value, the maximum number of four-element
  // floating-point, integer, or boolean vectors that can be held in
  // uniform variable storage for a vertex shader.
  // The value must be at least 128. See glUniform.
  var gl = obj.gl;
  var maxVertexUniformVectors = gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS);
  return Math.floor((maxVertexUniformVectors - 32) / 3);
};

/**
 * Someone bound a stream to this SkinEval. Enable the shader on the primitive,
 * and bind any additional weights or indices streams if necessary.
 *
 * @param {o3d.VertexSource} dest VertexSource that bound to this VertexSource.
 * @param {o3d.ParamVertexBufferStream} dest_param Other param which was bound.
 * @override
 */
o3d.SkinEval.prototype.streamWasBound_ = function(
    dest, semantic, semantic_index) {
  this.skin.updateVertexShader();

  if (this.skin.weights_field_ && this.skin.matrix_indices_field_) {
    var weights_stream = dest.getVertexStream(o3d.Stream.INFLUENCE_WEIGHTS, 0);
    var indices_stream = dest.getVertexStream(o3d.Stream.INFLUENCE_INDICES, 0);
    if (!weights_stream || !indices_stream ||
        weights_stream.field != this.skin.weights_field_ ||
        indices_stream.field != this.skin.matrix_indices_field_) {
      dest.setVertexStream(o3d.Stream.INFLUENCE_WEIGHTS, 0,
          this.skin.weights_field_, 0);
      dest.setVertexStream(o3d.Stream.INFLUENCE_INDICES, 0,
          this.skin.matrix_indices_field_, 0);
    }

    var destParam = dest.getParam("boneToWorld3x4");
    if (!destParam) {
      destParam = dest.createParam("boneToWorld3x4", "ParamParamArray");
    }
    destParam.bind(this.getParam("boneToWorld3x4"));

    destParam = dest.getParam("usingSkinShader");
    if (!destParam) {
      destParam = dest.createParam("usingSkinShader", "ParamFloat");
    }
    destParam.bind(this.getParam("usingSkinShader"));
  }
};

/**
 * Multiplies input by weight, and adds with and returns into output.
 *
 * @param {!Array<!Array<number>>} input Input matrix4 to weight.
 * @param {number} weight Amount to weight input matrix by.
 * @param {!Array<!Array<number>>} output The result of computing
 *     output += (input * weight)
 * @private
 */
o3d.SkinEval.prototype.multiplyAdd_ = function(input, weight, output) {
  var a0 = input[0];
  var a1 = input[1];
  var a2 = input[2];
  var a3 = input[3];
  var b0 = output[0];
  var b1 = output[1];
  var b2 = output[2];
  var b3 = output[3];
  b0[0] += a0[0] * weight;
  b0[1] += a0[1] * weight;
  b0[2] += a0[2] * weight;
  b0[3] += a0[3] * weight;
  b1[0] += a1[0] * weight;
  b1[1] += a1[1] * weight;
  b1[2] += a1[2] * weight;
  b1[3] += a1[3] * weight;
  b2[0] += a2[0] * weight;
  b2[1] += a2[1] * weight;
  b2[2] += a2[2] * weight;
  b2[3] += a2[3] * weight;
  b3[0] += a3[0] * weight;
  b3[1] += a3[1] * weight;
  b3[2] += a3[2] * weight;
  b3[3] += a3[3] * weight;
};

/**
 * Initializes all the input and output streams for this mesh, but does no
 * copying yet. You should follow with uninitStreams_ when you are done.
 *
 * @param {o3d.Skin} skin The skin.
 * @private
 */
o3d.SkinEval.prototype.initStreams_ = function(skin) {
  var ii, jj, ll, num_streams;

  var num_vertices = this.skin.influences.length;
  // Update our inputs, lock all the inputs and outputs and check that we have
  // the same number of vertices as vertex influences.
  for (ii = 0, num_streams = 0; ii < this.vertex_streams_.length; ++ii) {
    var array = this.vertex_streams_[ii];
    if (array) {
      for (jj = 0; jj < array.length; ++jj, ++num_streams) {
        var source_param = array[jj];

        // Make sure our upstream streams are ready
        var input = source_param.inputConnection;
        if (input && input.isAClassName("ParamVertexBufferStream")) {
          input.owner_.updateStreams();  // will automatically mark us as valid.
        } else {
          // Mark source_param as valid so we don't evaluate a second time.
          // TODO(pathorn): Caching previous computed values.
        }

        var source_stream = source_param.stream;
        if (source_stream.getMaxVertices_() != num_vertices) {
          // TODO: Change semantic below to semantic_name.
          this.gl.client.error_callback("SkinEval.doSkinning_: "
              + "stream " + source_stream.semantic + " index "
              + source_stream.semanticIndex + " in SkinEval '" + this.name
              + " does not have the same number of vertices as Skin '"
              + skin.name + "'");
          return;
        }

        // Lock this input.
        if (!this.input_stream_infos_[num_streams]) {
          this.input_stream_infos_[num_streams] = new o3d.SkinEval.StreamInfo;
        }
        if (!this.input_stream_infos_[num_streams].init(source_stream, false)) {
          var buffer_name;
          if (source_stream.field.buffer) {
            buffer_name = source_stream.field.buffer.name;
          }
          this.gl.client.error_callback("SkinEval.doSkinning_: "
              + "unable to lock buffer '" + buffer_name
              + " used by stream " + source_stream.semantic + " index "
              + source_stream.semanticIndex + " in SkinEval '" + this.name
              + "'");
          return;
        }

        // Lock the outputs to this input.
        var outputs = source_param.outputConnections; //ParamVector
        if (!this.output_stream_infos_[num_streams]) {
          this.output_stream_infos_[num_streams] = [];
        }
        var output_stream_info = this.output_stream_infos_[num_streams];
        output_stream_info.length = outputs.length;

        for (ll = 0; ll < outputs.length; ++ll) {
          var destination_param = outputs[ll];
          if (destination_param.isAClassName('ParamVertexBufferStream')) {
            // Mark destination_param valid so we don't evaluate a second time.
            // TODO(pathorn): Caching previous computed values.
          } else {
            this.gl.client.error_callback("SkinEval.doSkinning: "
                + destination_param.className + " not ParamVertexBufferStream");
          }
          var destination_stream = destination_param.stream;
          if (destination_stream.getMaxVertices_() != num_vertices) {
            this.gl.client.error_callback("SkinEval.doSkinning_: "
                + "stream " + destination_stream.semantic + " index "
                + destination_stream.semanticIndex + " targeted by SkinEval '"
                + this.name + " does not have the same number of vertices as "
                + "Skin '" + skin.name + "'");
            return;
          }

          if (!output_stream_info[ll]) {
            output_stream_info[ll] = new o3d.SkinEval.StreamInfo;
          }
          if (!output_stream_info[ll].init(destination_stream,true)) {
            var buffer_name;
            if (destination_stream.field.buffer) {
              buffer_name = destination_stream.field.buffer.name;
            }
            this.gl.client.error_callback("SkinEval.doSkinning_: "
                + "unable to lock buffer '" + buffer_name
                + " used by stream " + destination_stream.semantic + " index "
                + destination_stream.semanticIndex + " targeted by SkinEval '"
                + this.name + "'");
            return;
          }
        }
      }
    }
  }

  this.input_stream_infos_.length = num_streams;
  this.output_stream_infos_.length = num_streams;
};

/**
 * Saves the result of the skinning operation in the graphics hardware.
 *
 * @private
 */
o3d.SkinEval.prototype.uninitStreams_ = function() {
  // Unlock any buffers that were locked during skinning
  for (ii = 0; ii < this.input_stream_infos_.length; ++ii) {
    this.input_stream_infos_[ii].uninit();
  }
  for (ii = 0; ii < this.output_stream_infos_.length; ++ii) {
    var output_streams = this.output_stream_infos_[ii];
    for (var jj = 0; jj < output_streams.length; ++jj) {
      output_streams[jj].uninit();
    }
  }
};

/**
 * Does skinning in software. Performs the actual matrix-point mulitplications
 * and saves the result in all the output streams.
 *
 * @private
 */
o3d.SkinEval.prototype.doSkinning_ = function() {
  this.initStreams_();

  var ii, jj, ll;
  var influences_array = this.skin.influences;
  var num_vertices = influences_array.length;

  for (ii = 0; ii < num_vertices; ++ii) {
    var influences = influences_array[ii];
    if (influences.length) {
      // Even are vertices, odd are weights
      var this_matrix_index = influences[0];
      // Get the first matrix.
      var this_weight = influences[1];

      // combine the matrixes for this vertex.
      var accumulated_matrix =
          [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]];
      this.multiplyAdd_(this.bones_[this_matrix_index],
          this_weight, accumulated_matrix);
      var num_influences = influences.length;
      for (jj = 2; jj < num_influences; jj+=2) {
        var influence_matrix_index = influences[jj];
        var influence_weight = influences[jj + 1];
        this.multiplyAdd_(this.bones_[influence_matrix_index],
            influence_weight, accumulated_matrix);
      }

      // for each source, compute and copy to destination.
      for (jj = 0; jj < this.input_stream_infos_.length; ++jj) {
        var input_stream_info = this.input_stream_infos_[jj];
        input_stream_info.compute_function_(accumulated_matrix);
        var output_streams = this.output_stream_infos_[jj];
        var num_output_streams = output_streams.length;
        for (ll = 0; ll < num_output_streams; ++ll) {
          output_streams[ll].copy_function_(input_stream_info);
        }
      }
    }
  }

  this.uninitStreams_();
};

/**
 * Updates the bones from this.matrices.
 *
 * @private
 */
o3d.SkinEval.prototype.updateBones_ = function() {
  // Get our matrices.
  var param_array = this.matrices;
  if (!param_array) {
    this.gl.client.error_callback("SkinEval.updateBones_: "
        + "no matrices for SkinEval '" + this.name + "'");
    return;
  }

  var the_skin = this.skin;
  if (!the_skin) {
    this.gl.client.error_callback("SkinEval.updateBones_: "
        + "no skin specified in SkinEval '" + this.name + "'");
    return;
  }

  // Make sure the bone indices are in range.
  if (the_skin.getHighestMatrixIndex() >= param_array.length) {
    this.gl.client.error_callback("SkinEval.updateBones_: "
        + "skin '" + the_skin.name + " specified in SkinEval '"
        + this.name
        + "' references matrices outside the valid range in ParamArray '"
        + param_array.name + "'");
    return;
  }

  // Make sure the bind pose array size matches the matrices
  var inverse_bind_pose_array = the_skin.inverseBindPoseMatrices;
  if (inverse_bind_pose_array.length != param_array.length) {
    this.gl.client.error_callback("SkinEval.updateBones_: "
        + "skin '" + the_skin.name + " specified in SkinEval '"
        + this.name + "' and the ParamArray '"
        + param_array.name + "' do not have the same number of matrices.");
    return;
  }

  // Get the inverse of our base to remove from the bones.
  var inverse_base = this.temp_matrix_;
  o3d.Transform.inverse(this.base, inverse_base);

  for (var ii = 0; ii < param_array.length; ++ii) {
    var param = param_array.getParam(ii); // ParamMatrix4
    if (!param) {
      this.gl.client.error_callback("SkinEval.updateBones_: "
          + "In SkinEval '" + this.name + "' param at index " + ii
          + " in ParamArray '" + param_array.name
          + " is not a ParamMatrix4");
      return;
    }
    this.bones_[ii] = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];
    o3d.Transform.compose(param.value, inverse_bind_pose_array[ii],
                          this.bones_[ii]);
    o3d.Transform.compose(inverse_base, this.bones_[ii], this.bones_[ii]);
  }
};

/**
 * Updates the VertexBuffers bound to streams on this VertexSource.
 */
o3d.SkinEval.prototype.updateStreams = function() {
  if (this.disableShader || !this.usingSkinShader ||
      !this.skin.updateVertexShader()) {
    this.updateBones_();
    this.doSkinning_(this.skin);
    this.usingSkinShader = 0.0;
  }
};

/**
 * Should be called on boneToWorld3x4's get value.
 * Updates ParamArray for bones uniform sent to vertex shader.
 *
 * @param {ParamParamArrayOutput} param The array uniform parameter to update.
 * @return {ParamArray} The array containing all of the float4 params.
 */
o3d.SkinEval.prototype.updateOutputs = function(param) {
  this.updateBones_();
  if (!this.bone_array_) {
    this.bone_array_ = new o3d.ParamArray;
    this.bone_array_.gl = this.gl;
    var max_num_bones = o3d.SkinEval.getMaxNumBones(this);
    this.bone_array_.resize(max_num_bones * 3, "ParamFloat4");
    param.value = this.bone_array_;
  }
  var boneArray = this.bone_array_;
  var ii, jj;
  if (!this.disableShader && this.skin.updateVertexShader()) {
    // Is this the first time the param has been read?
    if (!this.usingSkinShader) {
      // If so, we disable skinning in software.
      this.usingSkinShader = 1.0;
      // Copy the default positions of all vertex buffers.
      this.initStreams_();
      var num_vertices = this.skin.influences.length;
      for (ii = 0; ii < this.input_stream_infos_.length; ++ii) {
        var input_stream_info = this.input_stream_infos_[ii];
        var output_streams = this.output_stream_infos_[ii];
        for (jj = 0; jj < output_streams.length; ++jj) {
          var values = input_stream_info.field_.getAt(0, num_vertices);
          output_streams[jj].field_.setAt(0, values);
        }
      }
      this.uninitStreams_();
    }
    var row;
    for (ii = 0; ii < this.bones_.length; ++ii) {
      var bone = this.bones_[ii];
      row = boneArray.getParam(ii*3);
      row.value[0] = bone[0][0];
      row.value[1] = bone[1][0];
      row.value[2] = bone[2][0];
      row.value[3] = bone[3][0];
      row = boneArray.getParam(ii*3 + 1);
      row.value[0] = bone[0][1];
      row.value[1] = bone[1][1];
      row.value[2] = bone[2][1];
      row.value[3] = bone[3][1];
      row = boneArray.getParam(ii*3 + 2);
      row.value[0] = bone[0][2];
      row.value[1] = bone[1][2];
      row.value[2] = bone[2][2];
      row.value[3] = bone[3][2];
    }
  }
  return boneArray;
};

/**
 * This class helps manage each stream. Because allocating memory is slow we
 * keep these around across calls and reuse them in place by calling init.
 *
 * @param {o3d.Stream} stream
 * @param {o3d.Buffer.AccessMode} access_mode
 * @constructor
 * @private
 */
o3d.SkinEval.StreamInfo = function() {
  this.compute_function_ = null;
  this.copy_function_ = null;
  this.result_ = null;
  this.field_ = null;
  this.values_ = null;
  this.buffer_ = null;
  this.index_ = 0;
  this.writable_ = false;
};


/**
 * Initialize this StreamInfo object from the given Stream.
 *
 * @param {o3d.Stream} stream Stream to lock.
 * @param {boolean} access_mode true if writable, false otherwise.
 * @return {boolean} True if the buffer lock was successful, false if error.
 */
o3d.SkinEval.StreamInfo.prototype.init = function(stream, access_mode) {
  if (this.values_ || this.buffer_) {
    return false;
  }
  var field = stream.field;
  var buffer = field.buffer;
  // field must be a FloatField, but in o3d-webgl, Field is the same type so
  // we can't check isAClassName.
  if (!buffer) {
    return false;
  }
  switch (field.numComponents) {
    case 3:
      this.copy_function_ = this.copyFloat3;
      this.compute_function_ = (stream.semantic == o3d.Stream.POSITION) ?
          this.computeFloat3AsPoint3 : this.computeFloat3AsVector3;
      break;
    case 4:
      this.compute_function_ = this.computeFloat4AsVector4;
      this.copy_function_ = this.copyFloat4;
      break;
    default:
      return false;
  }

  buffer.lock();
  this.field_ = field;
  this.buffer_ = buffer;
  this.values_ = buffer.array_;
  this.index_ = this.field_.offset_;
  this.writable_ = access_mode;
  this.stride_ = buffer.totalComponents;
  return true;
};

/**
 * Uninitialize this StreamInfo object, and unlock the stream.
 * Can be reused for another init() call.
 */
o3d.SkinEval.StreamInfo.prototype.uninit = function() {
  if (this.buffer_) {
    if (this.writable_) {
      this.buffer_.unlock();
    }
    this.buffer_ = null;
    this.field_ = null;
    this.values_ = null;
  }
};

/**
 * Consumes the next 3 values from this.values_.
 * Multiplies the current value by the matrix and stores it in result_ and
 * advances to the next value.
 *
 * @param {!Array<!Array<number>>} matrix matrix4 to apply to the vector3.
 */
o3d.SkinEval.StreamInfo.prototype.computeFloat3AsVector3 = function(matrix) {
  var ii = this.index_;
  var vec = [this.values_[ii], this.values_[ii + 1], this.values_[ii + 2], 0];
  this.result_ = o3d.Transform.multiplyVector(matrix, vec);
  this.index_ = ii + this.stride_;
};

/**
 * Consumes the next 3 values from this.values_.
 * Multiplies the current value by the matrix and stores it in result_ and
 * advances to the next value.
 *
 * @param {!Array<!Array<number>>} matrix matrix4 to apply to the vector3.
 */
o3d.SkinEval.StreamInfo.prototype.computeFloat3AsPoint3 = function(matrix) {
  var ii = this.index_;
  // TODO: The C++ code just dropped element 3 of the return Vector4, while
  // o3d.Transform.transformPoint divides by the last value to make it 1.
  // Which is the right one to use?
  var point = [this.values_[ii], this.values_[ii + 1], this.values_[ii + 2], 1];
  this.result_ = o3d.Transform.multiplyVector(matrix, point);
  this.index_ = ii + this.stride_;
};

/**
 * Consumes the next 4 this.values_.
 * Multiplies the current value by the matrix and stores it in result_ and
 * advances to the next value.
 *
 * @param {!Array<!Array<number>>} matrix matrix4 to apply to the vector4.
 */
o3d.SkinEval.StreamInfo.prototype.computeFloat4AsVector4 = function(matrix) {
  var ii = this.index_;
  var vec = [this.values_[ii], this.values_[ii + 1], this.values_[ii + 2],
             this.values_[ii + 3]];
  this.result_ = o3d.Transform.multiplyVector(matrix, vec);
  this.index_ = ii + this.stride_;
};

/**
 * Copies the Float3 result_ from source and advances to the next value.
 *
 * @param {!o3d.SkinEval.StreamInfo} source Source StreamInfo to copy from.
 */
o3d.SkinEval.StreamInfo.prototype.copyFloat3 = function(source) {
  var ii = this.index_;
  this.values_[ii] = source.result_[0];
  this.values_[ii+1] = source.result_[1];
  this.values_[ii+2] = source.result_[2];
  this.index_ = ii + this.stride_;
};

/**
 * Copies the Float4 result_ from source and advances to the next value.
 *
 * @param {!o3d.SkinEval.StreamInfo} source Source StreamInfo to copy from.
 */
o3d.SkinEval.StreamInfo.prototype.copyFloat4 = function(source) {
  var ii = this.index_;
  this.values_[ii] = source.result_[0];
  this.values_[ii+1] = source.result_[1];
  this.values_[ii+2] = source.result_[2];
  this.values_[ii+3] = source.result_[3];
  this.index_ = ii + this.stride_;
};

/*
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview Base for all o3d sample utilties.
 *    For more information about o3d see
 *    http://code.google.com/p/o3d.
 *
 *
 * The main point of this module is to provide a central place to
 * have an init function to register an o3d namespace object because many other
 * modules need access to it.
 */

/**
 * A namespace for all the o3djs utility libraries.
 * @namespace
 */
var o3djs = o3djs || {};

/**
 * Define this because the Google internal JSCompiler needs goog.typedef below.
 */
var goog = goog || {};

/**
 * A macro for defining composite types.
 *
 * By assigning goog.typedef to a name, this tells Google internal JSCompiler
 * that this is not the name of a class, but rather it's the name of a composite
 * type.
 *
 * For example,
 * /** @type {Array|NodeList} / goog.ArrayLike = goog.typedef;
 * will tell JSCompiler to replace all appearances of goog.ArrayLike in type
 * definitions with the union of Array and NodeList.
 *
 * Does nothing in uncompiled code.
 */
goog.typedef = true;

/**
 * Reference to the global context.  In most cases this will be 'window'.
 */
o3djs.global = this;

/**
 * Flag used to force a function to run in the browser when it is called
 * from V8.
 * @type {boolean}
 */
o3djs.BROWSER_ONLY = true;

/**
 * Array of namespaces that have been provided.
 * @private
 * @type {!Array.<string>}
 */
o3djs.provided_ = [];

/**
 * Creates object stubs for a namespace. When present in a file,
 * o3djs.provide also indicates that the file defines the indicated
 * object.
 * @param {string} name name of the object that this file defines.
 */
o3djs.provide = function(name) {
  // Ensure that the same namespace isn't provided twice.
  if (o3djs.getObjectByName(name) &&
      !o3djs.implicitNamespaces_[name]) {
    throw 'Namespace "' + name + '" already declared.';
  }

  var namespace = name;
  while ((namespace = namespace.substring(0, namespace.lastIndexOf('.')))) {
    o3djs.implicitNamespaces_[namespace] = true;
  }

  o3djs.exportPath_(name);
  o3djs.provided_.push(name);
};


/**
 * Namespaces implicitly defined by o3djs.provide. For example,
 * o3djs.provide('o3djs.events.Event') implicitly declares
 * that 'o3djs' and 'o3djs.events' must be namespaces.
 *
 * @type {Object}
 * @private
 */
o3djs.implicitNamespaces_ = {};

/**
 * Builds an object structure for the provided namespace path,
 * ensuring that names that already exist are not overwritten. For
 * example:
 * "a.b.c" -> a = {};a.b={};a.b.c={};
 * Used by o3djs.provide and o3djs.exportSymbol.
 * @param {string} name name of the object that this file defines.
 * @param {Object} opt_object the object to expose at the end of the path.
 * @param {Object} opt_objectToExportTo The object to add the path to; default
 *     is |o3djs.global|.
 * @private
 */
o3djs.exportPath_ = function(name, opt_object, opt_objectToExportTo) {
  var parts = name.split('.');
  var cur = opt_objectToExportTo || o3djs.global;
  var part;

  // Internet Explorer exhibits strange behavior when throwing errors from
  // methods externed in this manner.  See the testExportSymbolExceptions in
  // base_test.html for an example.
  if (!(parts[0] in cur) && cur.execScript) {
    cur.execScript('var ' + parts[0]);
  }

  // Parentheses added to eliminate strict JS warning in Firefox.
  while (parts.length && (part = parts.shift())) {
    if (!parts.length && o3djs.isDef(opt_object)) {
      // last part and we have an object; use it.
      cur[part] = opt_object;
    } else if (cur[part]) {
      cur = cur[part];
    } else {
      cur = cur[part] = {};
    }
  }
};


/**
 * Returns an object based on its fully qualified external name.  If you are
 * using a compilation pass that renames property names beware that using this
 * function will not find renamed properties.
 *
 * @param {string} name The fully qualified name.
 * @param {Object} opt_obj The object within which to look; default is
 *     |o3djs.global|.
 * @return {Object} The object or, if not found, null.
 */
o3djs.getObjectByName = function(name, opt_obj) {
  var parts = name.split('.');
  var cur = opt_obj || o3djs.global;
  for (var pp = 0; pp < parts.length; ++pp) {
    var part = parts[pp];
    if (cur[part]) {
      cur = cur[part];
    } else {
      return null;
    }
  }
  return cur;
};


/**
 * Implements a system for the dynamic resolution of dependencies.
 * @param {string} rule Rule to include, in the form o3djs.package.part.
 */
o3djs.require = function(rule) {
  // TODO(gman): For some unknown reason, when we call
  // o3djs.util.getScriptTagText_ it calls
  // document.getElementsByTagName('script') and for some reason the scripts do
  // not always show up. Calling it here seems to fix that as long as we
  // actually ask for the length, at least in FF 3.5.1 It would be nice to
  // figure out why.
  var dummy = document.getElementsByTagName('script').length;

  // if the object already exists we do not need do do anything
  if (o3djs.getObjectByName(rule)) {
    return;
  }
  var path = o3djs.getPathFromRule_(rule);
  if (path) {
    o3djs.included_[path] = true;
    o3djs.writeScripts_();
  } else {
    throw new Error('o3djs.require could not find: ' + rule);
  }
};


/**
 * Path for included scripts.
 * @type {string}
 */
o3djs.basePath = '';


/**
 * Object used to keep track of urls that have already been added. This
 * record allows the prevention of circular dependencies.
 * @type {Object}
 * @private
 */
o3djs.included_ = {};


/**
 * This object is used to keep track of dependencies and other data that is
 * used for loading scripts.
 * @private
 * @type {Object}
 */
o3djs.dependencies_ = {
  visited: {},  // used when resolving dependencies to prevent us from
                // visiting the file twice.
  written: {}  // used to keep track of script files we have written.
};


/**
 * Tries to detect the base path of the o3djs-base.js script that
 * bootstraps the o3djs libraries.
 * @private
 */
o3djs.findBasePath_ = function() {
  var doc = o3djs.global.document;
  if (typeof doc == 'undefined') {
    return;
  }
  if (o3djs.global.BASE_PATH) {
    o3djs.basePath = o3djs.global.BASE_PATH;
    return;
  } else {
    // HACKHACK to hide compiler warnings :(
    o3djs.global.BASE_PATH = null;
  }
  var scripts = doc.getElementsByTagName('script');
  for (var script, i = 0; script = scripts[i]; i++) {
    var src = script.src;
    var l = src.length;
    var basePathMatch = src.match(/(.*?)(o3djs\/base.js|o3d\.min\.js|o3d\.src\.js)/);
    if (basePathMatch) {
      o3djs.basePath = basePathMatch[1];
      return;
    }
  }
};


/**
 * Writes a script tag if, and only if, that script hasn't already been added
 * to the document.  (Must be called at execution time.)
 * @param {string} src Script source.
 * @private
 */
o3djs.writeScriptTag_ = function(src) {
  var doc = o3djs.global.document;
  if (typeof doc != 'undefined' &&
      !o3djs.dependencies_.written[src]) {
    o3djs.dependencies_.written[src] = true;
    doc.write('<script type="text/javascript" src="' +
              src + '"></' + 'script>');
  }
};


/**
 * Resolves dependencies based on the dependencies added using addDependency
 * and calls writeScriptTag_ in the correct order.
 * @private
 */
o3djs.writeScripts_ = function() {
  // the scripts we need to write this time.
  var scripts = [];
  var seenScript = {};
  var deps = o3djs.dependencies_;

  function visitNode(path) {
    if (path in deps.written) {
      return;
    }

    // we have already visited this one. We can get here if we have cyclic
    // dependencies.
    if (path in deps.visited) {
      if (!(path in seenScript)) {
        seenScript[path] = true;
        scripts.push(path);
      }
      return;
    }

    deps.visited[path] = true;

    if (!(path in seenScript)) {
      seenScript[path] = true;
      scripts.push(path);
    }
  }

  for (var path in o3djs.included_) {
    if (!deps.written[path]) {
      visitNode(path);
    }
  }

  for (var i = 0; i < scripts.length; i++) {
    if (scripts[i]) {
      o3djs.writeScriptTag_(o3djs.basePath + scripts[i]);
    } else {
      throw Error('Undefined script input');
    }
  }
};


/**
 * Looks at the dependency rules and tries to determine the script file that
 * fulfills a particular rule.
 * @param {string} rule In the form o3djs.namespace.Class or
 *     project.script.
 * @return {string?} Url corresponding to the rule, or null.
 * @private
 */
o3djs.getPathFromRule_ = function(rule) {
  var parts = rule.split('.');
  return parts.join('/') + '.js';
};

o3djs.findBasePath_();

/**
 * Returns true if the specified value is not |undefined|.
 * WARNING: Do not use this to test if an object has a property. Use the in
 * operator instead.
 * @param {*} val Variable to test.
 * @return {boolean} Whether variable is defined.
 */
o3djs.isDef = function(val) {
  return typeof val != 'undefined';
};


/**
 * Exposes an unobfuscated global namespace path for the given object.
 * Note that fields of the exported object *will* be obfuscated,
 * unless they are exported in turn via this function or
 * o3djs.exportProperty.
 *
 * <p>Also handy for making public items that are defined in anonymous
 * closures.
 *
 * ex. o3djs.exportSymbol('Foo', Foo);
 *
 * ex. o3djs.exportSymbol('public.path.Foo.staticFunction',
 *                        Foo.staticFunction);
 *     public.path.Foo.staticFunction();
 *
 * ex. o3djs.exportSymbol('public.path.Foo.prototype.myMethod',
 *                        Foo.prototype.myMethod);
 *     new public.path.Foo().myMethod();
 *
 * @param {string} publicPath Unobfuscated name to export.
 * @param {Object} object Object the name should point to.
 * @param {Object} opt_objectToExportTo The object to add the path to; default
 *     is |o3djs.global|.
 */
o3djs.exportSymbol = function(publicPath, object, opt_objectToExportTo) {
  o3djs.exportPath_(publicPath, object, opt_objectToExportTo);
};

/**
 * This string contains JavaScript code to initialize a new V8 instance.
 * @private
 * @type {string}
 */
o3djs.v8Initializer_ = '';

/**
 * This array contains references to objects that v8 needs to bind to when
 * it initializes.
 * @private
 * @type {!Array.<Object>}
 */
o3djs.v8InitializerArgs_ = [];

/**
 * Converts any JavaScript value to a string representation that when evaluated
 * will result in an equal value.
 * @param {*} value Any value.
 * @return {string} A string representation for the value.
 * @private
 */
o3djs.valueToString_ = function(value) {
  switch (typeof(value)) {
    case 'undefined':
      return 'undefined';
    case 'string':
      var escaped = escape(value);
      if (escaped === value) {
        return '"' + value + '"';
      } else {
        return 'unescape("' + escaped + '")';
      }
    case 'object':
      if (value === null) {
        return 'null';
      } else {
        // TODO: all the other builtin JavaScript objects like Date,
        // Number, Boolean, etc.
        if (value instanceof RegExp) {
          var result =
              'new RegExp(' + o3djs.valueToString_(value.source) + ', "';
          if (value.global) {
            result += 'g';
          }
          if (value.ignoreCase) {
            result += 'i';
          }
          if (value.multiline) {
            result += 'm';
          }
          result += '")';
          return result;
        } else if (o3djs.base.isArray(value)) {
          var valueAsArray = /** @type {!Array} */ (value);
          var result = '[';
          var separator = '';
          for (var i = 0; i < valueAsArray.length; ++i) {
            result += separator + o3djs.valueToString_(valueAsArray[i]);
            separator = ',';
          }
          result += ']\n';
          return result;
        } else {
          var valueAsObject = /** @type {!Object} */ (value);
          var result = '{\n';
          var separator = '';
          for (var propertyName in valueAsObject) {
            result += separator + '"' + propertyName + '": ' +
              o3djs.valueToString_(valueAsObject[propertyName]);
            separator = ',';
          }
          result += '}\n';
          return result;
        }
      }
    default:
      return value.toString()
  }
};

/**
 * Given an object holding a namespace and the name of that namespace,
 * generates a string that when evaluated will populate the namespace.
 * @param {!Object} namespaceObject An object holding a namespace.
 * @param {string} namespaceName The name of the namespace.
 * @param {!Array.<Object>} opt_args An array of objects that will be used
 *     together with the initializer string to populate a namespace. The args
 *     may be referenced from initializer code as args_[i] where i is the index
 *     in the array.
 * @return {string} A string that will populate the namespace.
 * @private
 */
o3djs.namespaceInitializer_ = function(namespaceObject,
                                       namespaceName,
                                       opt_args) {
  var result = namespaceName + ' = {};\n';
  for (var propertyName in namespaceObject) {
    var propertyNamespaceName = namespaceName + '.' + propertyName;
    var propertyValue = namespaceObject[propertyName];
    if (typeof(propertyValue) === 'object' && propertyValue !== null &&
        !o3djs.base.isArray(propertyValue) &&
        !(propertyValue instanceof RegExp)) {
      result += o3djs.namespaceInitializer_(propertyValue,
                                            propertyNamespaceName);
    } else {
      var valueAsString = o3djs.valueToString_(propertyValue);

      // If this is a browser only function then bind to the browser version
      // of the function rather than create a new function in V8.
      if (typeof(propertyValue) == 'function' &&
          valueAsString.indexOf('o3djs.BROWSER_ONLY') != -1) {
        valueAsString = 'args_[' + opt_args.length + ']';
        opt_args.push(propertyValue);
      }
      result += propertyNamespaceName + ' = ' + valueAsString + ';\n';

      if (typeof(propertyValue) === 'function' && propertyValue.prototype) {
        result += o3djs.namespaceInitializer_(
            propertyValue.prototype,
            propertyNamespaceName + '.prototype');
      }
    }
  }
  return result;
};

o3djs.provide('o3djs.base');

/**
 * The base module for o3djs.
 * @namespace
 */
o3djs.base = o3djs.base || {};

/**
 * The a Javascript copy of the o3d namespace object. (holds constants, enums,
 * etc...)
 * @type {o3d}
 */
o3djs.base.o3d = null;

/**
 * Whether or not we need to use GLSL instead of HLSL.
 * @type {boolean}
 */
o3djs.base.glsl = false;

/**
 * Snapshots the current state of all provided namespaces. This state will be
 * used to initialize future V8 instances. It is automatically
 * called by o3djs.util.makeClients.
 */
o3djs.base.snapshotProvidedNamespaces = function()  {
  // Snapshot the V8 initializer string from the current state of browser
  // JavaScript the first time this is called.
  o3djs.v8Initializer_ = 'function(args_) {\n';
  o3djs.v8InitializerArgs_ = [];
  for (var i = 0; i < o3djs.provided_.length; ++i) {
    var object = o3djs.getObjectByName(o3djs.provided_[i]);
    o3djs.v8Initializer_ += o3djs.namespaceInitializer_(
        /** @type {!Object} */ (object),
        o3djs.provided_[i],
        o3djs.v8InitializerArgs_);
  }

  o3djs.v8Initializer_ += '}\n';
};

/**
 * Initializes the o3djs.sample library in a v8 instance. This should be called
 * for every V8 instance that uses the sample library. It is automatically
 * called by o3djs.util.makeClients.
 * @param {!Element} clientObject O3D.Plugin Object.
 */
o3djs.base.initV8 = function(clientObject)  {
  var v8Init = function(initializer, args) {
    // Set up the o3djs namespace.
    var o3djsBrowser = o3djs;
    o3djs = {};
    o3djs.browser = o3djsBrowser;
    o3djs.global = (function() { return this; })();

    o3djs.require = function(rule) {}
    o3djs.provide = function(rule) {}

    // Evaluate the initializer string with the arguments containing bindings
    // to browser side objects.
    eval('(' + initializer + ')')(args);

    // Make sure this points to the o3d namespace for this particular
    // instance of the plugin.
    o3djs.base.o3d = plugin.o3d;

    // Save off if we need GLSL.
    o3djs.base.glsl = plugin.client.clientInfo.glsl;
  };

  clientObject.eval(v8Init.toString())(o3djs.v8Initializer_,
                                       o3djs.v8InitializerArgs_);
};

/**
 * Initializes the o3djs.sample library.
 * Basically all it does is record the o3djs.namespace object which is used by
 * other functions to look up o3d constants.
 *
 * @param {!Element} clientObject O3D.Plugin Object.
 */
o3djs.base.init = function(clientObject)  {
  function recursivelyCopyProperties(object) {
    var copy = {};
    var hasProperties = false;
    for (var key in object) {
      var property = object[key];
      if (typeof property == 'object' || typeof property == 'function') {
        property = recursivelyCopyProperties(property);
      }
      if (typeof property != 'undefined') {
        copy[key] = property;
        hasProperties = true;
      }
    }
    return hasProperties ? copy : undefined;
  }
  try {
    o3djs.base.o3d = recursivelyCopyProperties(clientObject.o3d);
  } catch (e) {
    // Firefox 2 raises an exception when trying to enumerate a NPObject
    o3djs.base.o3d = clientObject.o3d;
  }
  // Because of a bug in chrome, it is not possible for the browser to enumerate
  // the properties of an NPObject.
  // Chrome bug: http://code.google.com/p/chromium/issues/detail?id=5743
  o3djs.base.o3d = o3djs.base.o3d || clientObject.o3d;
  // Save off if we need GLSL.
  o3djs.base.glsl = clientObject.client.clientInfo.glsl;
};

/**
 * Determine whether a value is an array. Do not use instanceof because that
 * will not work for V8 arrays (the browser thinks they are Objects).
 * @param {*} value A value.
 * @return {boolean} Whether the value is an array.
 */
o3djs.base.isArray = function(value) {
  var valueAsObject = /** @type {!Object} */ (value);
  return typeof(value) === 'object' && value !== null &&
      'length' in valueAsObject && 'splice' in valueAsObject;
};

/**
 * Check if the o3djs library has been initialized.
 * @return {boolean} true if ready, false if not.
 */
o3djs.base.ready = function() {
  return o3djs.base.o3d != null;
};

/**
 * A stub for later optionally converting obfuscated names
 * @private
 * @param {string} name Name to un-obfuscate.
 * @return {string} un-obfuscated name.
 */
o3djs.base.maybeDeobfuscateFunctionName_ = function(name) {
  return name;
};

/**
 * Makes one class inherit from another.
 * @param {!Object} subClass Class that wants to inherit.
 * @param {!Object} superClass Class to inherit from.
 */
o3djs.base.inherit = function(subClass, superClass) {
  /**
   * TmpClass.
   * @ignore
   * @constructor
   */
  var TmpClass = function() { };
  TmpClass.prototype = superClass.prototype;
  subClass.prototype = new TmpClass();
};

/**
 * Parses an error stack from an exception
 * @param {!Exception} excp The exception to get a stack trace from.
 * @return {!Array.<string>} An array of strings of the stack trace.
 */
o3djs.base.parseErrorStack = function(excp) {
  var stack = [];
  var name;
  var line;

  if (!excp || !excp.stack) {
    return stack;
  }

  var stacklist = excp.stack.split('\n');

  for (var i = 0; i < stacklist.length - 1; i++) {
    var framedata = stacklist[i];

    name = framedata.match(/^([a-zA-Z0-9_$]*)/)[1];
    if (name) {
      name = o3djs.base.maybeDeobfuscateFunctionName_(name);
    } else {
      name = 'anonymous';
    }

    var result = framedata.match(/(.*:[0-9]+)$/);
    line = result && result[1];

    if (!line) {
      line = '(unknown)';
    }

    stack[stack.length] = name + ' : ' + line
  }

  // remove top level anonymous functions to match IE
  var omitRegexp = /^anonymous :/;
  while (stack.length && omitRegexp.exec(stack[stack.length - 1])) {
    stack.length = stack.length - 1;
  }

  return stack;
};

/**
 * Gets a function name from a function object.
 * @param {!function(...): *} aFunction The function object to try to get a
 *      name from.
 * @return {string} function name or 'anonymous' if not found.
 */
o3djs.base.getFunctionName = function(aFunction) {
  var regexpResult = aFunction.toString().match(/function(\s*)(\w*)/);
  if (regexpResult && regexpResult.length >= 2 && regexpResult[2]) {
    return o3djs.base.maybeDeobfuscateFunctionName_(regexpResult[2]);
  }
  return 'anonymous';
};

/**
 * Pretty prints an exception's stack, if it has one.
 * @param {Array.<string>} stack An array of errors.
 * @return {string} The pretty stack.
 */
o3djs.base.formatErrorStack = function(stack) {
  var result = '';
  for (var i = 0; i < stack.length; i++) {
    result += '> ' + stack[i] + '\n';
  }
  return result;
};

/**
 * Gets a stack trace as a string.
 * @param {number} stripCount The number of entries to strip from the top of the
 *     stack. Example: Pass in 1 to remove yourself from the stack trace.
 * @return {string} The stack trace.
 */
o3djs.base.getStackTrace = function(stripCount) {
  var result = '';

  if (typeof(arguments.caller) != 'undefined') { // IE, not ECMA
    for (var a = arguments.caller; a != null; a = a.caller) {
      result += '> ' + o3djs.base.getFunctionName(a.callee) + '\n';
      if (a.caller == a) {
        result += '*';
        break;
      }
    }
  } else { // Mozilla, not ECMA
    // fake an exception so we can get Mozilla's error stack
    var testExcp;
    try {
      eval('var var;');
    } catch (testExcp) {
      var stack = o3djs.base.parseErrorStack(testExcp);
      result += o3djs.base.formatErrorStack(stack.slice(3 + stripCount,
                                                        stack.length));
    }
  }

  return result;
};

/**
 * Sets the error handler on a client to a handler that displays an alert on the
 * first error.
 * @param {!o3d.Client} client The client object of the plugin.
 */
o3djs.base.setErrorHandler = function(client) {
  client.setErrorCallback(
      function(msg) {
        // Clear the error callback. Otherwise if the callback is happening
        // during rendering it's possible the user will not be able to
        // get out of an infinite loop of alerts.
        client.clearErrorCallback();
        alert('ERROR: ' + msg + '\n' + o3djs.base.getStackTrace(1));
      });
};

/**
 * Returns true if the user's browser is Microsoft IE.
 * @return {boolean} true if the user's browser is Microsoft IE.
 */
o3djs.base.IsMSIE = function() {
  var ua = navigator.userAgent.toLowerCase();
  var msie = /msie/.test(ua) && !/opera/.test(ua);
  return msie;
};
/*
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview This file contains various functions related to effects.
 * It puts them in the "effect" module on the o3djs object.
 *
 *     Note: This library is only a sample. It is not meant to be some official
 *     library. It is provided only as example code.
 *
 */

o3djs.provide('o3djs.effect');

/**
 * A Module for dealing with effects.
 * @namespace
 */
o3djs.effect = o3djs.effect || {};

/**
 * The name of standard 2 color checker effect.
 * @type {string}
 */
o3djs.effect.TWO_COLOR_CHECKER_EFFECT_NAME =
    'o3djs.effect.twoColorCheckerEffect';


/**
 * An object containing string constants and functions which are specific to
 * the o3d shading language.  When setLanguage gets called the properties of
 * this object get coppied into the o3djs.effect namespace and then get used
 * in shader generation code.
 * @namespace
 */
o3djs.effect.o3d = {
  LANGUAGE: 'o3d',
  FLOAT2: 'float2',
  FLOAT3: 'float3',
  FLOAT4: 'float4',
  MATRIX4: 'float4x4',
  MATRIX3: 'float3x3',
  MOD: 'fmod',
  ATTRIBUTE: '  ',
  ATTRIBUTE_PREFIX: 'input.',
  VARYING: '  ',
  VARYING_DECLARATION_PREFIX: '',
  VERTEX_VARYING_PREFIX: 'output.',
  PIXEL_VARYING_PREFIX: 'input.',
  TEXTURE: 'tex',
  SAMPLER: 'sampler',
  BEGIN_IN_STRUCT: 'struct InVertex {\n',
  BEGIN_OUT_STRUCT: 'struct OutVertex {\n',
  END_STRUCT: '};\n'
};


/**
 * An object containing string constants and functions which are specific to
 * the o3d shading language.  When setLanguage gets called the properties of
 * this object get coppied into the o3djs.effect namespace and then get used
 * in shader generation code.
 * @namespace
 */
o3djs.effect.glsl = {
    LANGUAGE: 'glsl',
    FLOAT2: 'vec2',
    FLOAT3: 'vec3',
    FLOAT4: 'vec4',
    MATRIX4: 'mat4',
    MATRIX3: 'mat3',
    MOD: 'mod',
    ATTRIBUTE: 'attribute ',
    ATTRIBUTE_PREFIX: '',
    VARYING: 'varying ',
    VARYING_DECLARATION_PREFIX: 'v_',
    VERTEX_VARYING_PREFIX: 'v_',
    PIXEL_VARYING_PREFIX: 'v_',
    TEXTURE: 'texture',
    SAMPLER: 'sampler2D',
    BEGIN_IN_STRUCT: '',
    BEGIN_OUT_STRUCT: '',
    END_STRUCT: '',
    // Only used in GLSL version of getAttributeName_.
    semanticNameMap: {
      'POSITION'  : 'position',
      'NORMAL'    : 'normal',
      'TANGENT'   : 'tangent',
      'BINORMAL'  : 'binormal',
      'COLOR'     : 'color',
      'TEXCOORD0' : 'texCoord0',
      'TEXCOORD1' : 'texCoord1',
      'TEXCOORD2' : 'texCoord2',
      'TEXCOORD3' : 'texCoord3',
      'TEXCOORD4' : 'texCoord4',
      'TEXCOORD5' : 'texCoord5',
      'TEXCOORD6' : 'texCoord6',
      'TEXCOORD7' : 'texCoord7'
    }
};


/**
 * The string that goes between the stream name and the semicolon to indicate
 * the semantic.
 * @param {string} name Name of the semantic.
 * @return {string}
 */
o3djs.effect.glsl.semanticSuffix = function(name) {
  return '';
};


/**
 * The string that goes between the stream name and the semicolon to indicate
 * the semantic.
 * @param {string} name Name of the semantic.
 * @return {string}
 */
o3djs.effect.o3d.semanticSuffix = function(name) {
  return ' : ' + name;
};


/**
 * Attribute variables in GLSL need to be named by their semantic in
 * order for the implementation to hook them up correctly.
 * @private
 */
o3djs.effect.glsl.getAttributeName_ = function(name, semantic) {
  var p = o3djs.effect;
  return p.semanticNameMap[semantic];
};


/**
 * This passes through the name in the Cg implementation.
 * @private
 */
o3djs.effect.o3d.getAttributeName_ = function(name, semantic) {
  return name;
};


/**
 * Generates code to multiply two things.
 * @param {string} a One multiplicand.
 * @param {string} b The other multiplicand.
 * @return {string}
 */
o3djs.effect.glsl.mul = function(a, b) {
  return '(' + b + ' * ' + a + ')';
};


/**
 * Generates code to multiply two things.
 * @param {string} a One multiplicand.
 * @param {string} b The other multiplicand.
 * @return {string}
 */
o3djs.effect.o3d.mul = function(a, b) {
  return 'mul(' + a + ', ' + b + ')';
};


/**
 * Generates code for some utility functions
 * (functions defined in cg but not glsl).
 * @return {string} The code for the utility functions.
 */
o3djs.effect.glsl.utilityFunctions = function() {
  return 'vec4 lit(float l ,float h, float m) {\n' +
         '  return vec4(1.0,\n' +
         '              max(l, 0.0),\n' +
         '              (l > 0.0) ? pow(max(0.0, h), m) : 0.0,\n' +
         '              1.0);\n' +
         '}\n';
};


/**
 * Generates code for some utility functions
 * (functions defined in cg but not glsl).
 * @return {string} The code for the utility functions.
 */
o3djs.effect.o3d.utilityFunctions = function() {
  return '';
};


/**
 * The string that starts the vertex shader main function.
 * @return {string} The effect code for the start of the main.
 */
o3djs.effect.glsl.beginVertexShaderMain = function() {
    return 'void main() {\n';
};

/**
 * The string that starts the vertex shader main function.
 * @return {string} The effect code for the start of the main.
 */
o3djs.effect.o3d.beginVertexShaderMain = function() {
  return 'OutVertex vertexShaderFunction(InVertex input) {\n' +
         '  OutVertex output;\n';
};

/**
 * The string that ends the vertex main function.
 * @return {string}
 */
o3djs.effect.glsl.endVertexShaderMain = function() {
  return '  gl_Position = ' + o3djs.effect.VERTEX_VARYING_PREFIX +
      'position;\n}\n';
};

/**
 * The string that ends the vertex main function.
 * @return {string}
 */
o3djs.effect.o3d.endVertexShaderMain = function() {
  return '  return output;\n}\n';
};


/**
 * The string that goes infront of the pixel shader main.
 * @param {!o3d.Material} material The material to inspect.
 * @param {boolean} diffuse Whether to include stuff for diffuse calculations.
 * @param {boolean} specular Whether to include stuff for diffuse
 *     calculations.
 * @param {boolean} bumpSampler Whether there is a bump sampler.
 * @return {string} The header.
 */
o3djs.effect.glsl.pixelShaderHeader =
    function(material, diffuse, specular, bumpSampler) {
  return '\n// #o3d SplitMarker\n';
};


/**
 * The string that goes infront of the pixel shader main.
 * @param {!o3d.Material} material The material to inspect.
 * @param {boolean} diffuse Whether to include stuff for diffuse calculations.
 * @param {boolean} specular Whether to include stuff for diffuse
 *     calculations.
 * @param {boolean} bumpSampler Whether there is a bump sampler.
 * @return {string} The header.
 */
o3djs.effect.o3d.pixelShaderHeader =
    function(material, diffuse, specular, bumpSampler) {
  return '';
};


/**
 * Repeats the declarations for the varying parameters if necessary.
 * @param {string} opt_decls The declarations if you know them already.
 * @return {string} Code for the parameter declarations.
 */
o3djs.effect.glsl.repeatVaryingDecls = function(opt_decls) {
  return (opt_decls ||
          o3djs.effect.varying_decls_ ||
          o3djs.buildVaryingDecls()) +
      '\n';
};

/**
 * Repeats the declarations for the varying parameters if necessary.
 * @param {string} opt_decls The declarations if you know them already.
 * @return {string} Code for the parameter declarations.
 */
o3djs.effect.o3d.repeatVaryingDecls = function(opt_decls) {
  return '';
};


/**
 * The string that goes infront of the pixel shader main.
 * @return {string} The effect code for the start of the main.
 */
o3djs.effect.glsl.beginPixelShaderMain = function() {
  return 'void main() {\n';
};


/**
 * The string that goes infront of the pixel shader main.
 * @return {string} The effect code for the start of the main.
 */
o3djs.effect.o3d.beginPixelShaderMain = function() {
  return 'float4 pixelShaderFunction(OutVertex input) : COLOR {\n';
};


/**
 * The string that goes at the end of the pixel shader main.
 * @param {string} color The code for the color to return.
 * @return {string} The effect code for the end of the main.
 */
o3djs.effect.o3d.endPixelShaderMain = function(color) {
  return '  return ' + color + ';\n}\n';
};


/**
 * The string that goes at the end of the pixel shader main.
 * @param {string} color The code for the color to return.
 * @return {string} The effect code for the end of the main.
 */
o3djs.effect.glsl.endPixelShaderMain = function(color) {
  return '  gl_FragColor = ' + color + ';\n}\n';
};


/**
 * The vertex and fragment shader entry point in the format that
 * o3d parses.
 * @return {string}
 */
o3djs.effect.o3d.entryPoints = function() {
  return '// #o3d VertexShaderEntryPoint vertexShaderFunction\n' +
    '// #o3d PixelShaderEntryPoint pixelShaderFunction\n';
};


/**
 * The vertex and fragment shader entry points.  In glsl, this is unnecessary.
 * @return {string}
 */
o3djs.effect.glsl.entryPoints = function() {
  return '';
};

o3djs.effect.glsl.matrixLoadOrder =
o3djs.effect.o3d.matrixLoadOrder = function() {
  return '// #o3d MatrixLoadOrder RowMajor\n';
};


/**
 * Sets the shader language used.  Passing 'glsl' will cause all generated
 * shader code to be in glsl.  Passing anything else will result in the
 * default o3d hlsl/cg based shader language.
 * @param {string} language Shader language to use.
 */
o3djs.effect.setLanguage = function(language) {
  var language_namespace = o3djs.effect.o3d;
  if (language == 'glsl') {
    language_namespace = o3djs.effect.glsl;
  }
  for (var f in o3djs.effect.glsl) {
    o3djs.effect[f] = language_namespace[f];
  }

  o3djs.effect.TWO_COLOR_CHECKER_FXSTRING =
      o3djs.effect.buildCheckerShaderString();
};

/**
 * Gets the language set in the function setLanguage.  Returns a string, either
 * 'glsl' or 'o3d'.
 */
o3djs.effect.getLanguage = function() {
  if (language_namespace == o3djs.effect.glsl) {
    return 'glsl';
  }
  return 'o3d';
};


/**
 * Builds the vertex attribute declarations for a given material.
 * @param {!o3d.Material} material The material to inspect.
 * @param {boolean} diffuse Whether to include stuff for diffuse calculations.
 * @param {boolean} specular Whether to include stuff for diffuse
 *     calculations.
 * @param {boolean} bumpSampler Whether there is a bump sampler.
 * @return {string} The code for the declarations.
 */
o3djs.effect.buildAttributeDecls =
    function(material, diffuse, specular, bumpSampler) {
  var str = o3djs.effect.BEGIN_IN_STRUCT +
            o3djs.effect.ATTRIBUTE + o3djs.effect.FLOAT4 + ' ' + 'position' +
            o3djs.effect.semanticSuffix('POSITION') + ';\n';
  if (diffuse || specular) {
    str += o3djs.effect.ATTRIBUTE + o3djs.effect.FLOAT3 + ' ' + 'normal' +
    o3djs.effect.semanticSuffix('NORMAL') + ';\n';
  }
  str += o3djs.effect.buildTexCoords(material, false) +
         o3djs.effect.buildBumpInputCoords(bumpSampler) +
         o3djs.effect.END_STRUCT;
  return str;
};


/**
 * Caches the varying parameter declarations to be repeated in the case that
 * we're in glsl and need to declare the varying parameters in both shaders.
 * @type {string}
 */
o3djs.effect.varying_decls_ = '';


/**
 * Builds the varying parameter declarations for a given material.
 * @param {!o3d.Material} material The material to inspect.
 * @param {boolean} diffuse Whether to include stuff for diffuse calculations.
 * @param {boolean} specular Whether to include stuff for diffuse
 *     calculations.
 * @param {boolean} bumpSampler Whether there is a bump sampler.
 * @return {string} The code for the declarations.
 */
o3djs.effect.buildVaryingDecls =
    function(material, diffuse, specular, bumpSampler) {
  var p = o3djs.effect;
  var str = p.BEGIN_OUT_STRUCT +
      p.VARYING + p.FLOAT4 + ' ' +
      p.VARYING_DECLARATION_PREFIX + 'position' +
      p.semanticSuffix('POSITION') + ';\n' +
      p.buildTexCoords(material, true) +
      p.buildBumpOutputCoords(bumpSampler);
  if (diffuse || specular) {
    str += p.VARYING + p.FLOAT3 + ' ' +
        p.VARYING_DECLARATION_PREFIX + 'normal' +
        p.semanticSuffix('TEXCOORD' +
           p.interpolant_++ + '') + ';\n' +
        p.VARYING + p.FLOAT3 + ' ' +
        p.VARYING_DECLARATION_PREFIX + 'surfaceToLight' +
        p.semanticSuffix(
            'TEXCOORD' + p.interpolant_++ + '') + ';\n';
  }
  if (specular) {
    str += p.VARYING + p.FLOAT3 + ' ' +
        p.VARYING_DECLARATION_PREFIX + 'surfaceToView' +
        p.semanticSuffix(
            'TEXCOORD' + p.interpolant_++ + '') + ';\n';
  }
  str += p.END_STRUCT;
  p.varying_decls_ = str;
  return str;
};


/**
 * An integer value which keeps track of the next available interpolant.
 * @type {number}
 * @private
 */
o3djs.effect.interpolant_ = 0;

/**
 * Builds the texture coordinate declaration for a given color input
 * (usually emissive, ambient, diffuse or specular).  If the color
 * input does not have a sampler, no TEXCOORD declaration is built.
 * @param {!o3d.Material} material The material to inspect.
 * @param {boolean} varying Whether these vertex declarations should
 *     be written as varying values.
 * @param {string} name The name of the color input.
 * @return {string} The code for the texture coordinate declaration.
 */
o3djs.effect.buildTexCoord = function(material, varying, name) {
  var p = o3djs.effect;
  // In the GLSL version we need to name the incoming attributes by
  // the semantic name in order for them to get hooked up correctly.
  if (material.getParam(name + 'Sampler')) {
    if (varying) {
      return '  ' + p.VARYING + p.FLOAT2 + ' ' +
          p.VARYING_DECLARATION_PREFIX + name + 'UV' +
          p.semanticSuffix(
              'TEXCOORD' + p.interpolant_++ + '') + ';\n';
    } else {
      var desiredName = name + 'UV';
      var semantic = 'TEXCOORD' + p.interpolant_++;
      var outputName = p.getAttributeName_(desiredName, semantic);
      if (p.semanticNameMap) {
        p.nameToSemanticMap_[desiredName] = semantic;
      }
      return '  ' + p.ATTRIBUTE + p.FLOAT2 + ' ' + outputName +
          p.semanticSuffix(semantic) + ';\n';
    }
  } else {
    return '';
  }
};

/**
 * Builds all the texture coordinate declarations for a vertex attribute
 * declaration.
 * @param {!o3d.Material} material The material to inspect.
 * @param {boolean} varying Whether these vertex declarations should
 *     be written as varying values.
 * @return {string} The code for the texture coordinate declarations.
 */
o3djs.effect.buildTexCoords = function(material, varying) {
  var p = o3djs.effect;
  p.interpolant_ = 0;
  if (!varying) {
    p.nameToSemanticMap_ = {};
  }
  return p.buildTexCoord(material, varying, 'emissive') +
         p.buildTexCoord(material, varying, 'ambient') +
         p.buildTexCoord(material, varying, 'diffuse') +
         p.buildTexCoord(material, varying, 'specular');
};


/**
 * Builds the texture coordinate passthrough statement for a given
 * color input (usually emissive, ambient, diffuse or specular).  These
 * assigments are used in the vertex shader to pass the texcoords to be
 * interpolated to the rasterizer.  If the color input does not have
 * a sampler, no code is generated.
 * @param {!o3d.Material} material The material to inspect.
 * @param {string} name The name of the color input.
 * @return {string} The code for the texture coordinate passthrough statement.
 */
o3djs.effect.buildUVPassthrough = function(material, name) {
  var p = o3djs.effect;
  if (material.getParam(name + 'Sampler')) {
    var sourceName = name + 'UV';
    var destName = sourceName;
    var semantic = p.nameToSemanticMap_[sourceName];
    if (semantic) {
      sourceName = p.getAttributeName_(sourceName, semantic);
    }
    return '  ' + p.VERTEX_VARYING_PREFIX + destName + ' = ' +
        p.ATTRIBUTE_PREFIX + sourceName + ';\n';
  } else {
    return '';
  }
};


/**
 * Builds all the texture coordinate passthrough statements for the
 * vertex shader.
 * @param {!o3d.Material} material The material to inspect.
 * @return {string} The code for the texture coordinate passthrough
 *                  statements.
 */
o3djs.effect.buildUVPassthroughs = function(material) {
  var p = o3djs.effect;
  // TODO(petersont): in the GLSL implementation we need to generate
  // the code for these attributes before we can pass their values
  // through, because in this implementation their names must be their
  // semantics (i.e., "texCoord4") rather than these chosen names.
  // Currently bumpUV is the only one which does not obey this rule.
  return p.buildUVPassthrough(material, 'emissive') +
         p.buildUVPassthrough(material, 'ambient') +
         p.buildUVPassthrough(material, 'diffuse') +
         p.buildUVPassthrough(material, 'specular') +
         p.buildUVPassthrough(material, 'bump');
};


/**
 * Builds bump input coords if needed.
 * @param {boolean} bumpSampler Whether there is a bump sampler.
 * @return {string} The code for bump input coords.
 */
o3djs.effect.buildBumpInputCoords = function(bumpSampler) {
  var p = o3djs.effect;
  return bumpSampler ?
      ('  ' + p.FLOAT3 + ' tangent' +
          p.semanticSuffix('TANGENT') + ';\n' +
       '  ' + p.FLOAT3 + ' binormal' +
          p.semanticSuffix('BINORMAL') + ';\n' +
       '  ' + p.FLOAT2 + ' bumpUV' +
          p.semanticSuffix(
              'TEXCOORD' + p.interpolant_++) + ';\n') : '';
};


/**
 * Builds bump output coords if needed.
 * @param {boolean} bumpSampler Whether there is a bump sampler.
 * @return {string} The code for bump input coords.
 */
o3djs.effect.buildBumpOutputCoords = function(bumpSampler) {
  var p = o3djs.effect;
  return bumpSampler ?
      (p.VARYING + p.FLOAT3 + ' ' +
	      p.VARYING_DECLARATION_PREFIX + 'tangent' +
          p.semanticSuffix(
              'TEXCOORD' + p.interpolant_++) + ';\n' +
       p.VARYING + p.FLOAT3 + ' ' +
	      p.VARYING_DECLARATION_PREFIX + 'binormal' +
          p.semanticSuffix(
		      'TEXCOORD' + p.interpolant_++) + ';\n' +
       p.VARYING + p.FLOAT2 + ' ' +
	      p.VARYING_DECLARATION_PREFIX + 'bumpUV' +
          p.semanticSuffix(
              'TEXCOORD' + p.interpolant_++) + ';\n') : '';
};


/**
 * Builds vertex and fragment shader string for a 2-color checker effect.
 * @return {string} The effect code for the shader, ready to be parsed.
 */
o3djs.effect.buildCheckerShaderString = function() {
  var p = o3djs.effect;
  var varyingDecls = p.BEGIN_OUT_STRUCT +
    p.VARYING + p.FLOAT4 + ' ' +
    p.VARYING_DECLARATION_PREFIX + 'position' +
    p.semanticSuffix('POSITION') + ';\n' +
    p.VARYING + p.FLOAT2 + ' ' +
    p.VARYING_DECLARATION_PREFIX + 'texCoord' +
    p.semanticSuffix('TEXCOORD0') + ';\n' +
    p.VARYING + p.FLOAT3 + ' ' +
    p.VARYING_DECLARATION_PREFIX + 'normal' +
    p.semanticSuffix('TEXCOORD1') + ';\n' +
    p.VARYING + p.FLOAT3 + ' ' +
    p.VARYING_DECLARATION_PREFIX + 'worldPosition' +
    p.semanticSuffix('TEXCOORD2') + ';\n' +
    p.END_STRUCT;

  return 'uniform ' + p.MATRIX4 + ' worldViewProjection' +
    p.semanticSuffix('WORLDVIEWPROJECTION') + ';\n' +
    'uniform ' + p.MATRIX4 + ' worldInverseTranspose' +
    p.semanticSuffix('WORLDINVERSETRANSPOSE') + ';\n' +
    'uniform ' + p.MATRIX4 + ' world' +
    p.semanticSuffix('WORLD') + ';\n' +
    '\n' +
    p.BEGIN_IN_STRUCT +
    p.ATTRIBUTE + p.FLOAT4 + ' position' +
    p.semanticSuffix('POSITION') + ';\n' +
    p.ATTRIBUTE + p.FLOAT3 + ' normal' +
    p.semanticSuffix('NORMAL') + ';\n' +
    p.ATTRIBUTE + p.FLOAT2 + ' texCoord0' +
    p.semanticSuffix('TEXCOORD0') + ';\n' +
    p.END_STRUCT +
    '\n' +
    varyingDecls +
    '\n' +
    p.beginVertexShaderMain() +
    '  ' + p.VERTEX_VARYING_PREFIX + 'position = ' +
    p.mul(p.ATTRIBUTE_PREFIX + 'position',
        'worldViewProjection') + ';\n' +
    '  ' + p.VERTEX_VARYING_PREFIX + 'normal = ' +
    p.mul(p.FLOAT4 + '(' +
    p.ATTRIBUTE_PREFIX + 'normal, 0.0)',
        'worldInverseTranspose') + '.xyz;\n' +
    '  ' + p.VERTEX_VARYING_PREFIX + 'worldPosition = ' +
        p.mul(p.ATTRIBUTE_PREFIX + 'position', 'world') +
    '.xyz;\n' +
    '  ' + p.VERTEX_VARYING_PREFIX + 'texCoord = ' +
    p.ATTRIBUTE_PREFIX + 'texCoord0;\n' +
    p.endVertexShaderMain() +
    '\n' +
    p.pixelShaderHeader() +
    'uniform ' + p.FLOAT4 + ' color1;\n' +
    'uniform ' + p.FLOAT4 + ' color2;\n' +
    'uniform float checkSize;\n' +
    'uniform ' + p.FLOAT3 + ' lightWorldPos;\n' +
    'uniform ' + p.FLOAT3 + ' lightColor;\n' +
    '\n' +
    p.repeatVaryingDecls(varyingDecls) +
    p.FLOAT4 + ' checker(' + p.FLOAT2 + ' uv) {\n' +
    '  float fmodResult = ' + p.MOD + '(' +
    '    floor(checkSize * uv.x) + \n' +
    '    floor(checkSize * uv.y), 2.0);\n' +
    '  return (fmodResult < 1.0) ? color1 : color2;\n' +
    '}\n\n' +
    p.beginPixelShaderMain() +
    '  ' + p.FLOAT3 + ' surfaceToLight = \n' +
    '      normalize(lightWorldPos - ' +
    p.PIXEL_VARYING_PREFIX + 'worldPosition);\n' +
    '  ' + p.FLOAT3 + ' worldNormal = normalize(' +
    p.PIXEL_VARYING_PREFIX + 'normal);\n' +
    '  ' + p.FLOAT4 + ' check = checker(' +
    p.PIXEL_VARYING_PREFIX + 'texCoord);\n' +
    '  float directionalIntensity = \n' +
    '      clamp(dot(worldNormal, surfaceToLight), 0.0, 1.0);\n' +
    '  ' + p.FLOAT4 +
    ' outColor = directionalIntensity * check;\n' +
    p.endPixelShaderMain(
        p.FLOAT4 + '(outColor.rgb, check.a)') +
    '\n' +
    p.entryPoints() +
    p.matrixLoadOrder();
};



/**
 * The name of the parameter on a material if it's a collada standard
 * material.
 *
 * NOTE: This parameter is just a string attached to a material. It has no
 *     meaning to the plugin, it is passed from the conditioner to the
 *     javascript libraries so that they can build collada like effects.
 *
 * @type {string}
 */
o3djs.effect.COLLADA_LIGHTING_TYPE_PARAM_NAME = 'collada.lightingType';

/**
 * The collada standard lighting types.
 * @type {!Object}
 */
o3djs.effect.COLLADA_LIGHTING_TYPES = {phong: 1,
                                       lambert: 1,
                                       blinn: 1,
                                       constant: 1};

/**
 * The FCollada standard materials sampler parameter name prefixes.
 * @type {!Array.<string>}
 */
o3djs.effect.COLLADA_SAMPLER_PARAMETER_PREFIXES = ['emissive',
                                                   'ambient',
                                                   'diffuse',
                                                   'specular',
                                                   'bump'];

/**
 * Check if lighting type is a collada lighting type.
 * @param {string} lightingType Lighting type to check.
 * @return {boolean} true if it's a collada lighting type.
 */
o3djs.effect.isColladaLightingType = function(lightingType) {
  return o3djs.effect.COLLADA_LIGHTING_TYPES[lightingType.toLowerCase()] == 1;
};

/**
 * Returns the collada lighting type of a collada standard material.
 * @param {!o3d.Material} material Material to get lighting type from.
 * @return {string} The lighting type or "" if it's not a collada standard
 *     material.
 */
o3djs.effect.getColladaLightingType = function(material) {
  var lightingTypeParam = material.getParam(
      o3djs.effect.COLLADA_LIGHTING_TYPE_PARAM_NAME);
  if (lightingTypeParam) {
    var lightingType = lightingTypeParam.value.toLowerCase();
    if (o3djs.effect.isColladaLightingType(lightingType)) {
      return lightingType;
    }
  }
  return '';
};

/**
 * Get the number of TEXCOORD streams needed by this material.
 * @param {!o3d.Material} material The material MUST be a standard
 *     collada material.
 * @return {number} The number oc TEXCOORD streams needed.
 */
o3djs.effect.getNumTexCoordStreamsNeeded = function(material) {
  var p = o3djs.effect;
  var lightingType = p.getColladaLightingType(material);
  if (!p.isColladaLightingType(lightingType)) {
    throw 'not a collada standard material';
  }
  var colladaSamplers = p.COLLADA_SAMPLER_PARAMETER_PREFIXES;
  var numTexCoordStreamsNeeded = 0;
  for (var cc = 0; cc < colladaSamplers.length; ++cc) {
    var samplerPrefix = colladaSamplers[cc];
    var samplerParam = material.getParam(samplerPrefix + 'Sampler');
    if (samplerParam) {
      ++numTexCoordStreamsNeeded;
    }
  }
  return numTexCoordStreamsNeeded;
};

/**
 * Loads shader source from an external file and creates shaders for an effect.
 * @param {!o3d.Effect} effect The effect to create the shaders in.
 * @param {string} url The url of the shader source.
 */
o3djs.effect.loadEffect = function(effect, url) {
  var fxString = o3djs.io.loadTextFileSynchronous(url);
  effect.loadFromFXString(fxString);
};

/**
 * Creates an effect from a file.
 * If the effect already exists in the pack that effect will be returned.
 * @param {!o3d.Pack} pack Pack to create effect in.
 * @param {string} url Url for effect file.
 * @return {!o3d.Effect} The effect.
 */
o3djs.effect.createEffectFromFile = function(pack, url) {
  var p = o3djs.effect;
  var effect = pack.getObjects(url, 'o3d.Effect')[0];
  if (!effect) {
    effect = pack.createObject('Effect');
    p.loadEffect(effect, url);
    effect.name = url;
  }
  return effect;
};

/**
 * Builds a shader string for a given standard COLLADA material type.
 *
 * @param {!o3d.Material} material Material for which to build the shader.
 * @param {string} effectType Type of effect to create ('phong', 'lambert',
 *     'constant').
 * @return {{description: string, shader: string}} A description and the shader
 *     string.
 */
o3djs.effect.buildStandardShaderString = function(material,
                                                  effectType) {
  var p = o3djs.effect;
  var bumpSampler = material.getParam('bumpSampler');
  var bumpUVInterpolant;

  /**
   * Extracts the texture type from a texture param.
   * @param {!o3d.ParamTexture} textureParam The texture parameter to
   *     inspect.
   * @return {string} The texture type (1D, 2D, 3D or CUBE).
   */
  var getTextureType = function(textureParam) {
    var texture = textureParam.value;
    if (!texture) return '2D';  // No texture value, have to make a guess.
    switch (texture.className) {
      case 'o3d.Texture1D' : return '1D';
      case 'o3d.Texture2D' : return '2D';
      case 'o3d.Texture3D' : return '3D';
      case 'o3d.TextureCUBE' : return 'CUBE';
      default : return '2D';
    }
  };

  /**
   * Extracts the sampler type from a sampler param.  It does it by inspecting
   * the texture associated with the sampler.
   * @param {!o3d.ParamTexture} samplerParam The texture parameter to
   *     inspect.
   * @return {string} The texture type (1D, 2D, 3D or CUBE).
   */
  var getSamplerType = function(samplerParam) {
    var sampler = samplerParam.value;
    if (!sampler) return '2D';
    var textureParam = sampler.getParam('Texture');
    if (textureParam)
      return getTextureType(textureParam);
    else
      return '2D';
  };

  /**
   * Builds uniform variables common to all standard lighting types.
   * @return {string} The effect code for the common shader uniforms.
   */
  var buildCommonVertexUniforms = function() {
    return 'uniform ' + p.MATRIX4 + ' worldViewProjection' +
        p.semanticSuffix('WORLDVIEWPROJECTION') + ';\n' +
        'uniform ' + p.FLOAT3 + ' lightWorldPos;\n';
  };

  /**
   * Builds uniform variables common to all standard lighting types.
   * @return {string} The effect code for the common shader uniforms.
   */
  var buildCommonPixelUniforms = function() {
    return 'uniform ' + p.FLOAT4 + ' lightColor;\n';
  };

  /**
   * Builds uniform variables common to lambert, phong and blinn lighting types.
   * @return {string} The effect code for the common shader uniforms.
   */
  var buildLightingUniforms = function() {
    return 'uniform ' + p.MATRIX4 + ' world' +
        p.semanticSuffix('WORLD') + ';\n' +
        'uniform ' + p.MATRIX4 +
        ' viewInverse' + p.semanticSuffix('VIEWINVERSE') + ';\n' +
        'uniform ' + p.MATRIX4 + ' worldInverseTranspose' +
        p.semanticSuffix('WORLDINVERSETRANSPOSE') + ';\n';
  };

  /**
   * Builds uniform parameters for a given color input.  If the material
   * has a sampler parameter, a sampler uniform is created, otherwise a
   * float4 color value is created.
   * @param {!o3d.Material} material The material to inspect.
   * @param {!Array.<string>} descriptions Array to add descriptions too.
   * @param {string} name The name of the parameter to look for.  Usually
   *     emissive, ambient, diffuse or specular.
   * @param {boolean} opt_addColorParam Whether to add a color param if no
   *     sampler exists. Default = true.
   * @return {string} The effect code for the uniform parameter.
   */
  var buildColorParam = function(material, descriptions, name,
                                 opt_addColorParam) {
    if (opt_addColorParam === undefined) {
      opt_addColorParam = true;
    }
    var samplerParam = material.getParam(name + 'Sampler');
    if (samplerParam) {
      var type = getSamplerType(samplerParam);
      descriptions.push(name + type + 'Texture');
      return 'uniform sampler' + type + ' ' + name + 'Sampler;\n';
    } else if (opt_addColorParam) {
      descriptions.push(name + 'Color');
      return 'uniform ' + p.FLOAT4 + ' ' + name + ';\n';
    } else {
      return '';
    }
  };

  /**
   * Builds the effect code to retrieve a given color input.  If the material
   * has a sampler parameter of that name, a texture lookup is done.  Otherwise
   * it's a no-op, since the value is retrieved directly from the color uniform
   * of that name.
   * @param {!o3d.Material} material The material to inspect.
   * @param {string} name The name of the parameter to look for.  Usually
   *                      emissive, ambient, diffuse or specular.
   * @return {string} The effect code for the uniform parameter retrieval.
   */
  var getColorParam = function(material, name) {
    var samplerParam = material.getParam(name + 'Sampler');
    if (samplerParam) {
      var type = getSamplerType(samplerParam);
      return '  ' + p.FLOAT4 + ' ' + name + ' = ' + p.TEXTURE + type +
             '(' + name + 'Sampler, ' +
             p.PIXEL_VARYING_PREFIX + name + 'UV);\n';
    } else {
      return '';
    }
  };

  /**
   * Builds vertex and fragment shader string for the Constant lighting type.
   * @param {!o3d.Material} material The material for which to build
   *     shaders.
   * @param {!Array.<string>} descriptions Array to add descriptions too.
   * @return {string} The effect code for the shader, ready to be parsed.
   */
  var buildConstantShaderString = function(material, descriptions) {
    descriptions.push('constant');
    return buildCommonVertexUniforms() +
           buildVertexDecls(material, false, false) +
           p.beginVertexShaderMain() +
           positionVertexShaderCode() +
           p.buildUVPassthroughs(material) +
           p.endVertexShaderMain() +
           p.pixelShaderHeader(material, false, false, bumpSampler) +
           buildCommonPixelUniforms() +
           p.repeatVaryingDecls() +
           buildColorParam(material, descriptions, 'emissive') +
           p.beginPixelShaderMain() +
           getColorParam(material, 'emissive') +
           p.endPixelShaderMain('emissive') +
           p.entryPoints() +
           p.matrixLoadOrder();
  };

  /**
   * Builds vertex and fragment shader string for the Lambert lighting type.
   * @param {!o3d.Material} material The material for which to build
   *     shaders.
   * @param {!Array.<string>} descriptions Array to add descriptions too.
   * @return {string} The effect code for the shader, ready to be parsed.
   */
  var buildLambertShaderString = function(material, descriptions) {
    descriptions.push('lambert');
    return buildCommonVertexUniforms() +
           buildLightingUniforms() +
           buildVertexDecls(material, true, false) +
           p.beginVertexShaderMain() +
           p.buildUVPassthroughs(material) +
           positionVertexShaderCode() +
           normalVertexShaderCode() +
           surfaceToLightVertexShaderCode() +
           bumpVertexShaderCode() +
           p.endVertexShaderMain() +
           p.pixelShaderHeader(material, true, false) +
           buildCommonPixelUniforms() +
           p.repeatVaryingDecls() +
           buildColorParam(material, descriptions, 'emissive') +
           buildColorParam(material, descriptions, 'ambient') +
           buildColorParam(material, descriptions, 'diffuse') +
           buildColorParam(material, descriptions, 'bump', false) +
           p.utilityFunctions() +
           p.beginPixelShaderMain() +
           getColorParam(material, 'emissive') +
           getColorParam(material, 'ambient') +
           getColorParam(material, 'diffuse') +
           getNormalShaderCode() +
           '  ' + p.FLOAT3 + ' surfaceToLight = normalize(' +
           p.PIXEL_VARYING_PREFIX + 'surfaceToLight);\n' +
           '  ' + p.FLOAT4 +
           ' litR = lit(dot(normal, surfaceToLight), 0.0, 0.0);\n' +
           p.endPixelShaderMain(p.FLOAT4 +
           '((emissive +\n' +
           '      lightColor *' +
           ' (ambient * diffuse + diffuse * litR.y)).rgb,\n' +
           '          diffuse.a)') +
           p.entryPoints() +
           p.matrixLoadOrder();
  };

  /**
   * Builds vertex and fragment shader string for the Blinn lighting type.
   * @param {!o3d.Material} material The material for which to build
   *     shaders.
   * @param {!Array.<string>} descriptions Array to add descriptions too.
   * @return {string} The effect code for the shader, ready to be parsed.
   * TODO: This is actually just a copy of the Phong code.
   *     Change to Blinn.
   */
  var buildBlinnShaderString = function(material, descriptions) {
    descriptions.push('phong');
    return buildCommonVertexUniforms() +
        buildLightingUniforms() +
        buildVertexDecls(material, true, true) +
        p.beginVertexShaderMain() +
        p.buildUVPassthroughs(material) +
        positionVertexShaderCode() +
        normalVertexShaderCode() +
        surfaceToLightVertexShaderCode() +
        surfaceToViewVertexShaderCode() +
        bumpVertexShaderCode() +
        p.endVertexShaderMain() +
        p.pixelShaderHeader(material, true, true) +
        buildCommonPixelUniforms() +
        p.repeatVaryingDecls() +
        buildColorParam(material, descriptions, 'emissive') +
        buildColorParam(material, descriptions, 'ambient') +
        buildColorParam(material, descriptions, 'diffuse') +
        buildColorParam(material, descriptions, 'specular') +
        buildColorParam(material, descriptions, 'bump', false) +
        'uniform float shininess;\n' +
        'uniform float specularFactor;\n' +
        p.utilityFunctions() +
        p.beginPixelShaderMain() +
        getColorParam(material, 'emissive') +
        getColorParam(material, 'ambient') +
        getColorParam(material, 'diffuse') +
        getColorParam(material, 'specular') +
        getNormalShaderCode() +
        '  ' + p.FLOAT3 + ' surfaceToLight = normalize(' +
        p.PIXEL_VARYING_PREFIX + 'surfaceToLight);\n' +
        '  ' + p.FLOAT3 + ' surfaceToView = normalize(' +
        p.PIXEL_VARYING_PREFIX + 'surfaceToView);\n' +
        '  ' + p.FLOAT3 +
        ' halfVector = normalize(surfaceToLight + ' +
        p.PIXEL_VARYING_PREFIX + 'surfaceToView);\n' +
        '  ' + p.FLOAT4 +
        ' litR = lit(dot(normal, surfaceToLight), \n' +
        '                    dot(normal, halfVector), shininess);\n' +
        p.endPixelShaderMain( p.FLOAT4 +
        '((emissive +\n' +
        '  lightColor *' +
        ' (ambient * diffuse + diffuse * litR.y +\n' +
        '                        specular * litR.z *' +
        ' specularFactor)).rgb,\n' +
        '      diffuse.a)') +
        p.entryPoints() +
        p.matrixLoadOrder();
  };

  /**
   * Builds vertex and fragment shader string for the Phong lighting type.
   * @param {!o3d.Material} material The material for which to build
   *     shaders.
   * @param {!Array.<string>} descriptions Array to add descriptions too.
   * @return {string} The effect code for the shader, ready to be parsed.
   */
  var buildPhongShaderString = function(material, descriptions) {
    descriptions.push('phong');
    return buildCommonVertexUniforms() +
        buildLightingUniforms() +
        buildVertexDecls(material, true, true) +
        p.beginVertexShaderMain() +
        p.buildUVPassthroughs(material) +
        positionVertexShaderCode() +
        normalVertexShaderCode() +
        surfaceToLightVertexShaderCode() +
        surfaceToViewVertexShaderCode() +
        bumpVertexShaderCode() +
        p.endVertexShaderMain() +
        p.pixelShaderHeader(material, true, true) +
        buildCommonPixelUniforms() +
        p.repeatVaryingDecls() +
        buildColorParam(material, descriptions, 'emissive') +
        buildColorParam(material, descriptions, 'ambient') +
        buildColorParam(material, descriptions, 'diffuse') +
        buildColorParam(material, descriptions, 'specular') +
        buildColorParam(material, descriptions, 'bump', false) +
        'uniform float shininess;\n' +
        'uniform float specularFactor;\n' +
        p.utilityFunctions() +
        p.beginPixelShaderMain() +
        getColorParam(material, 'emissive') +
        getColorParam(material, 'ambient') +
        getColorParam(material, 'diffuse') +
        getColorParam(material, 'specular') +
        getNormalShaderCode() +
        '  ' + p.FLOAT3 + ' surfaceToLight = normalize(' +
        p.PIXEL_VARYING_PREFIX + 'surfaceToLight);\n' +
        '  ' + p.FLOAT3 + ' surfaceToView = normalize(' +
        p.PIXEL_VARYING_PREFIX + 'surfaceToView);\n' +
        '  ' + p.FLOAT3 +
        ' halfVector = normalize(surfaceToLight + surfaceToView);\n' +
        '  ' + p.FLOAT4 +
        ' litR = lit(dot(normal, surfaceToLight), \n' +
        '                    dot(normal, halfVector), shininess);\n' +
        p.endPixelShaderMain(p.FLOAT4 +
        '((emissive +\n' +
        '  lightColor * (ambient * diffuse + diffuse * litR.y +\n' +
        '                        specular * litR.z *' +
        ' specularFactor)).rgb,\n' +
        '      diffuse.a)') +
        p.entryPoints() +
        p.matrixLoadOrder();
  };

  /**
   * Builds the position code for the vertex shader.
   * @return {string} The code for the vertex shader.
   */
  var positionVertexShaderCode = function() {
    return '  ' + p.VERTEX_VARYING_PREFIX + 'position = ' +
        p.mul(p.ATTRIBUTE_PREFIX +
        'position', 'worldViewProjection') + ';\n';
  };

  /**
   * Builds the normal code for the vertex shader.
   * @return {string} The code for the vertex shader.
   */
  var normalVertexShaderCode = function() {
    return '  ' + p.VERTEX_VARYING_PREFIX + 'normal = ' +
        p.mul(p.FLOAT4 + '(' +
        p.ATTRIBUTE_PREFIX +
        'normal, 0)', 'worldInverseTranspose') + '.xyz;\n';
  };

  /**
   * Builds the surface to light code for the vertex shader.
   * @return {string} The code for the vertex shader.
   */
  var surfaceToLightVertexShaderCode = function() {
    return '  ' + p.VERTEX_VARYING_PREFIX +
        'surfaceToLight = lightWorldPos - \n' +
           '                          ' +
           p.mul(p.ATTRIBUTE_PREFIX + 'position',
              'world') + '.xyz;\n';
  };

  /**
   * Builds the surface to view code for the vertex shader.
   * @return {string} The code for the vertex shader.
   */
  var surfaceToViewVertexShaderCode = function() {
    return '  ' + p.VERTEX_VARYING_PREFIX +
        'surfaceToView = (viewInverse[3] - ' +
         p.mul(p.ATTRIBUTE_PREFIX + 'position', 'world') + ').xyz;\n';
  };

  /**
   * Builds the normal map part of the vertex shader.
   * @param {boolean} opt_bumpSampler Whether there is a bump
   *     sampler. Default = false.
   * @return {string} The code for normal mapping in the vertex shader.
   */
  var bumpVertexShaderCode = function(opt_bumpSampler) {
    return bumpSampler ?
        ('  ' + p.VERTEX_VARYING_PREFIX + 'binormal = ' +
         p.mul(p.FLOAT4 + '(' +
         p.ATTRIBUTE_PREFIX + 'binormal, 0)',
             'worldInverseTranspose') + '.xyz;\n' +
         '  ' + p.VERTEX_VARYING_PREFIX + 'tangent = ' +
         p.mul(p.FLOAT4 +
         '(' + p.ATTRIBUTE_PREFIX + 'tangent, 0)',
             'worldInverseTranspose') + '.xyz;\n') : '';
  };

  /**
   * Builds the normal calculation of the pixel shader.
   * @return {string} The code for normal computation in the pixel shader.
   */
  var getNormalShaderCode = function() {
    return bumpSampler ?
        ('  ' + p.MATRIX3 + ' tangentToWorld = ' + p.MATRIX3 +
            '(' + p.PIXEL_VARYING_PREFIX + 'tangent,\n' +
         '                                   ' +
         p.PIXEL_VARYING_PREFIX + 'binormal,\n' +
         '                                   ' +
         p.PIXEL_VARYING_PREFIX + 'normal);\n' +
         '  ' + p.FLOAT3 + ' tangentNormal = ' + p.TEXTURE + '2D' + '(bumpSampler, ' +
         p.PIXEL_VARYING_PREFIX + 'bumpUV.xy).xyz -\n' +
         '                       ' + p.FLOAT3 +
         '(0.5, 0.5, 0.5);\n' + '  ' + p.FLOAT3 + ' normal = ' +
         p.mul('tangentNormal', 'tangentToWorld') + ';\n' +
         'normal = normalize(normal);\n') :
		 '  ' + p.FLOAT3 + ' normal = normalize(' +
         p.PIXEL_VARYING_PREFIX + 'normal);\n';
  };

  /**
   * Builds the vertex declarations for a given material.
   * @param {!o3d.Material} material The material to inspect.
   * @param {boolean} diffuse Whether to include stuff for diffuse
   *     calculations.
   * @param {boolean} specular Whether to include stuff for diffuse
   *     calculations.
   * @return {string} The code for the vertex declarations.
   */
  var buildVertexDecls = function(material, diffuse, specular) {
    return p.buildAttributeDecls(
        material, diffuse, specular, bumpSampler) +
        p.buildVaryingDecls(
            material, diffuse, specular, bumpSampler);
  };


  // Create a shader string of the appropriate type, based on the
  // effectType.
  var str;
  var descriptions = [];
  if (effectType == 'phong') {
    str = buildPhongShaderString(material, descriptions);
  } else if (effectType == 'lambert') {
    str = buildLambertShaderString(material, descriptions);
  } else if (effectType == 'blinn') {
    str = buildBlinnShaderString(material, descriptions);
  } else if (effectType == 'constant') {
    str = buildConstantShaderString(material, descriptions);
  } else {
    throw ('unknown effect type "' + effectType + '"');
  }

  return {description: descriptions.join('_'), shader: str};
};

/**
 * Gets or builds a shader for given standard COLLADA material type.
 *
 * Looks at the material passed in and assigns it an Effect that matches its
 * Params. If a suitable Effect already exists in pack it will use that Effect.
 *
 * @param {!o3d.Pack} pack Pack in which to create the new Effect.
 * @param {!o3d.Material} material Material for which to build the shader.
 * @param {string} effectType Type of effect to create ('phong', 'lambert',
 *     'constant').
 * @return {o3d.Effect} The created effect.
 */
o3djs.effect.getStandardShader = function(pack,
                                          material,
                                          effectType) {
  var record = o3djs.effect.buildStandardShaderString(material,
                                                      effectType);
  var effects = pack.getObjectsByClassName('o3d.Effect');
  for (var ii = 0; ii < effects.length; ++ii) {
    if (effects[ii].name == record.description &&
        effects[ii].source == record.shader) {
      return effects[ii];
    }
  }
  var effect = pack.createObject('Effect');
  if (effect) {
    effect.name = record.description;
    if (effect.loadFromFXString(record.shader)) {
      return effect;
    }
    pack.removeObject(effect);
  }
  return null;
};

/**
 * Attaches a shader for a given standard COLLADA material type to the
 * material.
 *
 * Looks at the material passed in and assigns it an Effect that matches its
 * Params. If a suitable Effect already exists in pack it will use that Effect.
 *
 * @param {!o3d.Pack} pack Pack in which to create the new Effect.
 * @param {!o3d.Material} material Material for which to build the shader.
 * @param {!o3djs.math.Vector3} lightPos Position of the default light.
 * @param {string} effectType Type of effect to create ('phong', 'lambert',
 *     'constant').
 * @return {boolean} True on success.
 */
o3djs.effect.attachStandardShader = function(pack,
                                             material,
                                             lightPos,
                                             effectType) {
  var effect = o3djs.effect.getStandardShader(pack,
                                              material,
                                              effectType);
  if (effect) {
    material.effect = effect;
    effect.createUniformParameters(material);

    // Set a couple of the default parameters in the hopes that this will
    // help the user get something on the screen. We check to make sure they
    // are not connected to something otherwise we'll get an error.
    var param = material.getParam('lightWorldPos');
    if (param && !param.inputConnection) {
      param.value = lightPos;
    }
    var param = material.getParam('lightColor');
    if (param && !param.inputConnection) {
      param.value = [1, 1, 1, 1];
    }
    return true;
  } else {
    return false;
  }
};

/**
 * Creates the uniform parameters needed for an Effect on the given ParamObject.
 * @param {!o3d.Pack} pack Pack to create extra objects in like Samplers and
 *     ParamArrays.
 * @param {!o3d.Effect} effect Effect.
 * @param {!o3d.ParamObject} paramObject ParamObject on which to create Params.
 */
o3djs.effect.createUniformParameters = function(pack, effect, paramObject) {
  effect.createUniformParameters(paramObject);
  var infos = effect.getParameterInfo();
  for (var ii = 0; ii < infos.length; ++ii) {
    var info = infos[ii];
    if (info.sasClassName.length == 0) {
      if (info.numElements > 0) {
        var paramArray = pack.createObject('ParamArray');
        var param = paramObject.getParam(info.name);
        param.value = paramArray;
        paramArray.resize(info.numElements, info.className);
        if (info.className == 'o3d.ParamSampler') {
          for (var jj = 0; jj < info.numElements; ++jj) {
            var sampler = pack.createObject('Sampler');
            paramArray.getParam(jj).value = sampler;
          }
        }
      } else if (info.className == 'o3d.ParamSampler') {
        var sampler = pack.createObject('Sampler');
        var param = paramObject.getParam(info.name);
        param.value = sampler;
      }
    }
  }
};

/**
 * Creates an effect that draws a 2 color procedural checker pattern.
 * @param {!o3d.Pack} pack The pack to create the effect in. If the pack
 *     already has an effect with the same name that effect will be returned.
 * @return {!o3d.Effect} The effect.
 */
o3djs.effect.createCheckerEffect = function(pack) {
  var effects = pack.getObjects(o3djs.effect.TWO_COLOR_CHECKER_EFFECT_NAME,
                                'o3d.Effect');
  if (effects.length > 0) {
    return effects[0];
  }

  var effect = pack.createObject('Effect');
  effect.loadFromFXString(o3djs.effect.TWO_COLOR_CHECKER_FXSTRING);
  effect.name = o3djs.effect.TWO_COLOR_CHECKER_EFFECT_NAME;
  return effect;
};


// For compatability with o3d code, the default language is o3d shading
// language.
o3djs.effect.setLanguage('o3d');


/*
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview This file contains various utility functions for o3d.  It
 * puts them in the "util" module on the o3djs object.
 *
 */

o3djs.provide('o3djs.util');

/**
 * A Module with various utilities.
 * @namespace
 */
o3djs.util = o3djs.util || {};

/**
 * The name of the o3d plugin. Used to find the plugin when checking
 * for its version.
 * @type {string}
 */
o3djs.util.PLUGIN_NAME = 'O3D Plugin';

/**
 * The version of the plugin needed to use this version of the javascript
 * utility libraries.
 * @type {string}
 */
o3djs.util.REQUIRED_VERSION = '0.1.42.4';

/**
 * The width an O3D must be to put a failure message inside
 * @type {number}
 */
o3djs.util.MINIMUM_WIDTH_FOR_MESSAGE = 200;

/**
 * The height an O3D must be to put a failure message inside
 * @type {number}
 */
o3djs.util.MINIMUM_HEIGHT_FOR_MESSAGE = 200;

/**
 * A URL at which to download the client.
 * @type {string}
 */
o3djs.util.PLUGIN_DOWNLOAD_URL = 'http://tools.google.com/dlpage/o3d';

/**
 * The Renderer InitStatus constants so we don't need an o3d object to look
 * them up.
 * @enum {number}
 */
o3djs.util.rendererInitStatus = {
  NO_PLUGIN: -1,
  UNINITIALIZED: 0,
  SUCCESS: 1,
  OUT_OF_RESOURCES: 2,
  GPU_NOT_UP_TO_SPEC: 3,
  INITIALIZATION_ERROR: 4
};

/**
 * This implements a JavaScript version of currying. Currying allows you to
 * take a function and fix its initial arguments, resulting in a function
 * expecting only the remaining arguments when it is invoked. For example:
 * <pre>
 * function add(a, b) {
 *   return a + b;
 * }
 * var increment = o3djs.util.curry(add, 1);
 * var result = increment(10);
 * </pre>
 * Now result equals 11.
 * @param {!function(...): *} func The function to curry.
 * @return {!function(...): *} The curried function.
 */
o3djs.util.curry = function(func) {
  var outerArgs = [];
  for (var i = 1; i < arguments.length; ++i) {
    outerArgs.push(arguments[i]);
  }
  return function() {
    var innerArgs = outerArgs.slice();
    for (var i = 0; i < arguments.length; ++i) {
      innerArgs.push(arguments[i]);
    }
    return func.apply(this, innerArgs);
  }
}

/**
 * Gets the URI in which the current page is located, omitting the file name.
 * @return {string} The base URI of the page. If the page is
 *     "http://some.com/folder/somepage.html" returns
 *     "http://some.com/folder/".
 */
o3djs.util.getCurrentURI = function() {
  var path = window.location.href;
  var index = path.lastIndexOf('/');
  return path.substring(0, index + 1);
};

/**
 * Given a URI that is relative to the current page, returns the absolute
 * URI.
 * @param {string} uri URI relative to the current page.
 * @return {string} Absolute uri. If the page is
 *     "http://some.com/folder/sompage.html" and you pass in
 *     "images/someimage.jpg" will return
 *     "http://some.com/folder/images/someimage.jpg".
 */
o3djs.util.getAbsoluteURI = function(uri) {
  return o3djs.util.getCurrentURI() + uri;
};

/**
 * Searches an array for a specific value.
 * @param {!Array.<*>} array Array to search.
 * @param {*} value Value to search for.
 * @return {boolean} True if value is in array.
 */
o3djs.util.arrayContains = function(array, value) {
  for (var i = 0; i < array.length; i++) {
    if (array[i] == value) {
      return true;
    }
  }
  return false;
};

/**
 * Searches for all transforms with a "o3d.tags" ParamString
 * that contains specific tag keywords assuming comma separated
 * words.
 * @param {!o3d.Transform} treeRoot Root of tree to search for tags.
 * @param {string} searchTags Tags to look for. eg "camera", "ogre,dragon".
 * @return {!Array.<!o3d.Transform>} Array of transforms.
 */
o3djs.util.getTransformsInTreeByTags = function(treeRoot, searchTags) {
  var splitTags = searchTags.split(',');
  var transforms = treeRoot.getTransformsInTree();
  var found = [];
  for (var n = 0; n < transforms.length; n++) {
    var tagParam = transforms[n].getParam('collada.tags');
    if (tagParam) {
       var tags = tagParam.value.split(',');
       for (var t = 0; t < tags.length; t++) {
         if (o3djs.util.arrayContains(splitTags, tags[t])) {
           found[found.length] = transforms[n];
           break;
         }
      }
    }
  }
  return found;
};

/**
 * Finds transforms in the tree by prefix.
 * @param {!o3d.Transform} treeRoot Root of tree to search.
 * @param {string} prefix Prefix to look for.
 * @return {!Array.<!o3d.Transform>} Array of transforms matching prefix.
 */
o3djs.util.getTransformsInTreeByPrefix = function(treeRoot, prefix) {
  var found = [];
  var transforms = treeRoot.getTransformsInTree();
  for (var ii = 0; ii < transforms.length; ii++) {
    var transform = transforms[ii];
    if (transform.name.indexOf(prefix) == 0) {
      found[found.length] = transform;
    }
  }
  return found;
};

/**
 * Finds the bounding box of all primitives in the tree, in the local space of
 * the tree root. This will use existing bounding boxes on transforms and
 * elements, but not create new ones.
 * @param {!o3d.Transform} treeRoot Root of tree to search.
 * @return {!o3d.BoundingBox} The boundinding box of the tree.
 */
o3djs.util.getBoundingBoxOfTree = function(treeRoot) {
  // If we already have a bounding box, use that one.
  var box = treeRoot.boundingBox;
  if (box.valid) {
    return box;
  }
  var o3d = o3djs.base.o3d;
  // Otherwise, create it as the union of all the children bounding boxes and
  // all the shape bounding boxes.
  var transforms = treeRoot.children;
  for (var i = 0; i < transforms.length; ++i) {
    var transform = transforms[i];
    var childBox = o3djs.util.getBoundingBoxOfTree(transform);
    if (childBox.valid) {
      // transform by the child local matrix.
//      childBox = childBox.mul(transform.localMatrix);
      if (box.valid) {
        box = box.add(childBox);
      } else {
        box = childBox;
      }
    }
  }
  var shapes = treeRoot.shapes;
  for (var i = 0; i < shapes.length; ++i) {
    var elements = shapes[i].elements;
    for (var j = 0; j < elements.length; ++j) {
      var elementBox = elements[j].boundingBox;
      if (!elementBox.valid) {
        elementBox = elements[j].getBoundingBox(0);
      }
      if (box.valid) {
        box = box.add(elementBox);
      } else {
        box = elementBox;
      }
    }
  }
  return box;
};

/**
 * Returns the smallest power of 2 that is larger than or equal to size.
 * @param {number} size Size to get power of 2 for.
 * @return {number} smallest power of 2 that is larger than or equal to size.
 */
o3djs.util.getPowerOfTwoSize = function(size) {
  var powerOfTwo = 1;
  size = size - 1;
  while (size) {
    size = size >> 1;
    powerOfTwo = powerOfTwo << 1;
  }
  return powerOfTwo;
};

/**
 * Gets the version of the installed plugin.
 * @return {?string} version string in 'major.minor.revision.build' format.
 *    If the plugin does not exist returns null.
 */
o3djs.util.getPluginVersion = function() {
  var version = null;
  var description = null;
  if (navigator.plugins != null && navigator.plugins.length > 0) {
    var plugin = navigator.plugins[o3djs.util.PLUGIN_NAME];
    if (plugin) {
      description = plugin.description;
    }
  } else if (o3djs.base.IsMSIE()) {
    try {
      var activeXObject = new ActiveXObject('o3d_host.O3DHostControl');
      description = activeXObject.description;
    } catch (e) {
      // O3D plugin was not found.
    }
  }
  if (description) {
    var re = /.*version:\s*(\d+)\.(\d+)\.(\d+)\.(\d+).*/;
    // Parse the version out of the description.
    var parts = re.exec(description);
    if (parts && parts.length == 5) {
      // make sure the format is #.#.#.#  no whitespace, no trailing comments
      version = '' + parseInt(parts[1], 10) + '.' +
                     parseInt(parts[2], 10) + '.' +
                     parseInt(parts[3], 10) + '.' +
                     parseInt(parts[4], 10);
    }
  }
  return version;
};

/**
 * Checks if the required version of the plugin in available.
 * @param {string} requiredVersion version string in
 *    "major.minor.revision.build" format. You can leave out any non-important
 *    numbers for example "3" = require major version 3, "2.4" = require major
 *    version 2, minor version 4.
 * @return {boolean} True if the required version is available.
 */
o3djs.util.requiredVersionAvailable = function(requiredVersion) {
  var version = o3djs.util.getPluginVersion();
  if (!version) {
    return false;
  }
  var haveParts = version.split('.');
  var requiredParts = requiredVersion.split('.');
  if (requiredParts.length > 4) {
    throw Error('requiredVersion has more than 4 parts!');
  }
  for (var pp = 0; pp < requiredParts.length; ++pp) {
    var have = parseInt(haveParts[pp], 10);
    var required = parseInt(requiredParts[pp], 10);
    if (have < required) {
      return false;
    }
    if (have > required) {
      return true;
    }
  }
  return true;
};

/**
 * Gets all the elements of a certain tag that have a certain id.
 * @param {string} tag The tag to look for. (eg. 'div').
 * @param {string} id The id to look for. This can be a regular expression.
 * @return {!Array.<!Element>} An array of the elements found.
 */
o3djs.util.getElementsByTagAndId = function(tag, id) {
  var elements = [];
  var allElements = document.getElementsByTagName(tag);
  for (var ee = 0; ee < allElements.length; ++ee) {
    var element = allElements[ee];
    if (element.id && element.id.match(id)) {
      elements.push(element);
    }
  }
  return elements;
};

/**
 * Gets all the Elements that contain or would contain O3D plugin objects.
 * @param {string} opt_id The id to look for. This can be a regular
 *     expression. The default is "^o3d".
 * @param {string} opt_tag The type of tag to look for. The default is "div".
 */
o3djs.util.getO3DContainerElements = function(opt_id, opt_tag) {
  var tag = opt_tag || 'div';
  var id = opt_id || '^o3d';
  return o3djs.util.getElementsByTagAndId(tag, id);
}

/**
 * Offers the user the option to download the plugin.
 *
 * Finds all divs with the id "^o3d" and inserts a message and link
 * inside to download the plugin. If no areas exist OR if none of them are
 * large enough for the message then displays an alert.
 *
 * @param {string} opt_id The id to look for. This can be a regular
 *     expression. The default is "^o3d".
 * @param {string} opt_tag The type of tag to look for. The default is "div".
 */
o3djs.util.offerPlugin = function(opt_id, opt_tag) {
  var havePlugin = o3djs.util.requiredVersionAvailable('');
  var elements = o3djs.util.getO3DContainerElements(opt_id, opt_tag);
  var addedMessage = false;
  // TODO: This needs to be localized OR we could insert a html like
  // <script src="http://google.com/o3d_plugin_dl"></script>
  // in which case google could serve the message localized and update the
  // link.
  var subMessage =
    (havePlugin ?
     'This page requires a newer version of the O3D plugin.' :
     'This page requires the O3D plugin to be installed.');
  var message =
      '<div style="background: lightblue; width: 100%; height: 100%; ' +
      'text-align:center;">' +
      '<br/><br/>' + subMessage + '<br/>' +
      '<a href="' + o3djs.util.PLUGIN_DOWNLOAD_URL +
      '">Click here to download.</a>' +
      '</div>'
  for (var ee = 0; ee < elements.length; ++ee) {
    var element = elements[ee];
    if (element.clientWidth >= o3djs.util.MINIMUM_WIDTH_FOR_MESSAGE &&
        element.clientHeight >= o3djs.util.MINIMUM_HEIGHT_FOR_MESSAGE &&
        element.style.display.toLowerCase() != 'none' &&
        element.style.visibility.toLowerCase() != 'hidden') {
      addedMessage = true;
      element.innerHTML = message;
    }
  }
  if (!addedMessage) {
    if (confirm(subMessage + '\n\nClick OK to download.')) {
      window.location = o3djs.util.PLUGIN_DOWNLOAD_URL;
    }
  }
};

/**
 * Tells the user their graphics card is not able to run the plugin or is out
 * of resources etc.
 *
 * Finds all divs with the id "^o3d" and inserts a message. If no areas
 * exist OR if none of them are large enough for the message then displays an
 * alert.
 *
 * @param {!o3d.Renderer.InitStatus} initStatus The initializaion status of
 *     the renderer.
 * @param {string} error An error message. Will be '' if there is no message.
 * @param {string} opt_id The id to look for. This can be a regular
 *     expression. The default is "^o3d".
 * @param {string} opt_tag The type of tag to look for. The default is "div".
 */
o3djs.util.informNoGraphics = function(initStatus, error, opt_id, opt_tag) {
  var elements = o3djs.util.getO3DContainerElements(opt_id, opt_tag);
  var addedMessage = false;
  var subMessage;
  var message;
  var alertMessage = '';
  var alertFunction = function() { };

  var moreInfo = function(error) {
    var html = '';
    if (error.length > 0) {
      html = '' +
          '<br/><br/><div>More Info:<br/>' + error + '</div>';
    }
    return html;
  };

  // TODO: This needs to be localized OR we could insert a html like
  // <script src="http://google.com/o3d_plugin_dl"></script>
  // in which case google could serve the message localized and update the
  // link.
  if (initStatus == o3djs.util.rendererInitStatus.GPU_NOT_UP_TO_SPEC) {
    subMessage =
        'We are terribly sorry but it appears your graphics card is not ' +
        'able to run o3d. We are working on a solution.';
    message =
        '<div style="background: lightgray; width: 100%; height: 100%; ' +
        'text-align: center;">' +
        '<br/><br/>' + subMessage +
        '<br/><br/><a href="' + o3djs.util.PLUGIN_DOWNLOAD_URL +
        '">Click Here to go the O3D website</a>' +
        moreInfo(error) +
        '</div>';
    alertMessage = '\n\nClick OK to go to the o3d website.';
    alertFunction = function() {
          window.location = o3djs.util.PLUGIN_DOWNLOAD_URL;
        };
  } else if (initStatus == o3djs.util.rendererInitStatus.OUT_OF_RESOURCES) {
    subMessage =
        'Your graphics system appears to be out of resources. Try closing ' +
        'some applications and then refreshing this page.';
    message =
        '<div style="background: lightgray; width: 100%; height: 100%; ' +
        'text-align: center;">' +
        '<br/><br/>' + subMessage +
        moreInfo(error) +
        '</div>';
  } else {
    subMessage =
        'A unknown error has prevented O3D from starting. Try downloading ' +
        'new drivers or checking for OS updates.';
    message =
        '<div style="background: lightgray; width: 100%; height: 100%; ' +
        'text-align: center;">' +
        '<br/><br/>' + subMessage +
        moreInfo(error) +
        '</div>';
  }
  for (var ee = 0; ee < elements.length; ++ee) {
    var element = elements[ee];
    if (element.clientWidth >= o3djs.util.MINIMUM_WIDTH_FOR_MESSAGE &&
        element.clientHeight >= o3djs.util.MINIMUM_HEIGHT_FOR_MESSAGE &&
        element.style.display.toLowerCase() != 'none' &&
        element.style.visibility.toLowerCase() != 'hidden') {
      addedMessage = true;
      element.innerHTML = message;
    }
  }
  if (!addedMessage) {
    if (confirm(subMessage + alertMessage)) {
      alertFunction();
    }
  }
};

/**
 * Handles failure to create the plugin.
 *
 * @param {!o3d.Renderer.InitStatus} initStatus The initializaion status of
 *     the renderer.
 * @param {string} error An error message. Will be '' if there is no message.
 * @param {string} opt_id The id to look for. This can be a regular
 *     expression. The default is "^o3d".
 * @param {string} opt_tag The type of tag to look for. The default is "div".
 */
o3djs.util.informPluginFailure = function(initStatus, error, opt_id, opt_tag) {
  if (initStatus == o3djs.util.rendererInitStatus.NO_PLUGIN) {
    o3djs.util.offerPlugin(opt_id, opt_tag);
  } else {
    o3djs.util.informNoGraphics(initStatus, error, opt_id, opt_tag);
  }
};

/**
 * Utility to get the text contents of a DOM element with a particular ID.
 * Currently only supports textarea and script nodes.
 * @param {string} id The Node id.
 * @return {string} The text content.
 */
o3djs.util.getElementContentById = function(id) {
  // DOM manipulation is not currently supported in IE.
  o3djs.BROWSER_ONLY = true;

  var node = document.getElementById(id);
  if (!node) {
    throw 'getElementContentById could not find node with id ' + id;
  }
  switch (node.tagName) {
    case 'TEXTAREA':
      return node.value;
    case 'SCRIPT':
      return node.text;
    default:
      throw 'getElementContentById does not no how to get content from a ' +
          node.tagName + ' element';
  }
};

/**
 * Utility to get an element from the DOM by ID. This must be used from V8
 * in preference to document.getElementById because we do not currently
 * support invoking methods on DOM objects in IE.
 * @param {string} id The Element id.
 * @return {Element} The Element or null if not found.
 */
o3djs.util.getElementById = function(id) {
  o3djs.BROWSER_ONLY = true;
  return document.getElementById(id);
};

/**
 * Identifies a JavaScript engine.
 * @enum {number}
 */
o3djs.util.Engine = {
  /**
   * The JavaScript engine provided by the browser.
   */
  BROWSER: 0,
  /**
   * The V8 JavaScript engine embedded in the plugin.
   */
  V8: 1
};

/**
 * The engine selected as the main engine (the one the makeClients callback
 * will be invoked on).
 * @private
 * @type {o3djs.util.Engine}
 */
o3djs.util.mainEngine_ = o3djs.util.Engine.BROWSER;

/**
 * Checks the user agent string for substring s, returning true if it appears.
 * @return {boolean} Whether the browser's user-agent string contains string s.
 */
function o3djs_navHas(s) {
  return navigator.userAgent.indexOf(s) != -1;
}

/**
 * Checks for V8 support. This is to cope with environments where our V8 is
 * known to be problematic, eg Safari on 10.6.
 * @return {boolean} Whether the environment supports V8.
 */
function o3djs_isV8Supported() {
  if (o3djs_navHas('Chrome'))
    return true;
  if (!o3djs_navHas('Safari'))
    return true;
  return !o3djs_navHas('Intel Mac OS X 10_6');
}

/**
 * Select an engine to use as the main engine (the one the makeClients
 * callback will be invoked on). If an embedded engine is requested, one
 * element must be identified with the id 'o3d'. The callback will be invoked
 * in this element.
 * Ignores attempts to choose V8 if it is not supported in this host.
 * @param {o3djs.util.Engine} engine The engine.
 */
o3djs.util.setMainEngine = function(engine) {
  if ((engine == o3djs.util.Engine.V8) && !o3djs_isV8Supported()) {
    engine = o3djs.util.Engine.BROWSER;
  }
  o3djs.util.mainEngine_ = engine;
};

/**
 * A regex used to cleanup the string representation of a function before
 * it is evaled.
 * @private
 * @type {!RegExp}
 */
o3djs.util.fixFunctionString_ = /^\s*function\s+[^\s]+\s*\(([^)]*)\)/

/**
 * Evaluate a callback function in the V8 engine.
 * @param {!Object} clientElement The plugin containing the V8 engine.
 * @param {!function(...): *} callback A function to call.
 * @param {!Object} thisArg The value to be bound to "this".
 * @param {!Array.<*>} args The arguments to pass to the callback.
 * @return {*} The result of calling the callback.
 */
o3djs.util.callV8 = function(clientElement, callback, thisArg, args) {
  // Sometimes a function will be converted to a string like this:
  //   function foo(a, b) { ... }
  // In this case, convert to this form:
  //   function(a, b) { ... }
  var functionString = callback.toString();
  functionString = functionString.replace(o3djs.util.fixFunctionString_,
                                          'function($1)');

  // Make a V8 function that will invoke the callback.
  var v8Code =
      'function(thisArg, args) {\n' +
      '  var localArgs = [];\n' +
      '  var numArgs = args.length;\n' +
      '  for (var i = 0; i < numArgs; ++i) {\n' +
      '    localArgs.push(args[i]);\n' +
      '  }\n' +
      '  var func = ' + functionString + ';\n' +
      '  return func.apply(thisArg, localArgs);\n' +
      '}\n';

  // Evaluate the function in V8.
  var v8Function = clientElement.eval(v8Code);
  return v8Function(thisArg, args);
};

/**
 * A regex to remove .. from a URI.
 * @private
 * @type {!RegExp}
 */
o3djs.util.stripDotDot_ = /\/[^\/]+\/\.\./;

/**
 * Turn a URI into an absolute URI.
 * @param {string} uri The URI.
 * @return {string} The absolute URI.
 */
o3djs.util.toAbsoluteUri = function(uri) {
  if (uri.indexOf('://') == -1) {
    var baseUri = document.location.toString();
    var lastSlash = baseUri.lastIndexOf('/');
    if (lastSlash != -1) {
      baseUri = baseUri.substring(0, lastSlash);
    }
    uri = baseUri + '/' + uri;
  }

  do {
    var lastUri = uri;
    uri = uri.replace(o3djs.util.stripDotDot_, '');
  } while (lastUri !== uri);

  return uri;
};

/**
 * The script URIs.
 * @private
 * @type {!Array.<string>}
 */
o3djs.util.scriptUris_ = [];

/**
 * Add a script URI. Scripts that are referenced from script tags that are
 * within this URI are automatically loaded into the alternative JavaScript
 * main JavaScript engine. Do not include directories of scripts that are
 * included with o3djs.require. These are always available. This mechanism
 * is not able to load scripts in a different domain from the document.
 * @param {string} uri The URI.
 */
o3djs.util.addScriptUri = function(uri) {
  o3djs.util.scriptUris_.push(o3djs.util.toAbsoluteUri(uri));
};

/**
 * Determine whether a URI is a script URI that should be loaded into the
 * alternative main JavaScript engine.
 * @param {string} uri The URI.
 * @return {boolean} Whether it is a script URI.
 */
o3djs.util.isScriptUri = function(uri) {
  uri = o3djs.util.toAbsoluteUri(uri);
  for (var i = 0; i < o3djs.util.scriptUris_.length; ++i) {
    var scriptUri = o3djs.util.scriptUris_[i];
    if (uri.substring(0, scriptUri.length) === scriptUri) {
      return true;
    }
  }
  return false;
};

/**
 * Returns whether or not this is a script tag we want. Currently that is
 * only script tags with an id that starts with "o3d".
 * @private
 * @param {!Element} scriptElement The script element to check.
 * @return {boolean} True if we want this script tag.
 */
o3djs.util.isWantedScriptTag_ = function(scriptElement) {
  return scriptElement.id && scriptElement.id.match(/^o3dscript/);
};

/**
 * Concatenate the text of all the script tags in the document and invokes
 * the callback when complete. This function is asynchronous if any of the
 * script tags reference JavaScript through a URI.
 * @private
 * @return {string} The script tag text.
 */
o3djs.util.getScriptTagText_ = function() {
  var scriptTagText = '';
  var scriptElements = document.getElementsByTagName('script');
  for (var i = 0; i < scriptElements.length; ++i) {
    var scriptElement = scriptElements[i];
    if (scriptElement.type === '' ||
        scriptElement.type === 'text/javascript') {
      if ('text' in scriptElement && scriptElement.text &&
          o3djs.util.isWantedScriptTag_(scriptElement)) {
        scriptTagText += scriptElement.text;
      }
      if ('src' in scriptElement && scriptElement.src &&
          o3djs.util.isScriptUri(scriptElement.src)) {
        // It would be better to make this an asynchronous load but the script
        // file is very likely to be in the browser cache because it should
        // have just been loaded via the browser script tag.
        scriptTagText += o3djs.io.loadTextFileSynchronous(scriptElement.src);
      }
    }
  }
  return scriptTagText;
};

/**
 * Creates a client element.  In other words it creates an <OBJECT> tag for o3d.
 * <b>Note that the browser may not have initialized the plugin before
 * returning.</b>
 * @param {!Element} element The DOM element under which the client element
 *    will be appended.
 * @param {string} opt_features A comma separated list of the
 *    features you need for your application. The current list of features:
 *    <li>FloatingPointTextures: Includes the formats R32F, ABGR16F and
 *    ABGR32F</li>
 *    The features are case sensitive.
 * @param {string} opt_requestVersion version string in
 *    "major.minor.revision.build" format. You can leave out any non-important
 *    numbers for example "3" = request major version 3, "2.4" = request major
 *    version 2, minor version 4. If no string is passed in the newest version
 *    of the plugin will be created.
 * @return {Element} O3D element or null if requested version is not
 *    available.
 */
o3djs.util.createClient = function(element, opt_features, opt_requestVersion) {
  opt_features = opt_features || '';
  opt_requestVersion = opt_requestVersion || o3djs.util.REQUIRED_VERSION;
  if (!o3djs.util.requiredVersionAvailable(opt_requestVersion)) {
    return null;
  }
  opt_features += (opt_features ? ',' : '') + 'APIVersion=' +
                  opt_requestVersion;
  var objElem;
  // TODO: Use opt_requiredVersion to set a version so the plugin
  //    can make sure it offers that version of the API.
  // Note:  The IE version of the plug-in does not receive attributes during
  //  construction, unless the innerHTML construction style is used.
  if (o3djs.base.IsMSIE()) {
    element.innerHTML =
        '<OBJECT ' +
          'WIDTH="100%" HEIGHT="100%"' +
          'CLASSID="CLSID:9666A772-407E-4F90-BC37-982E8160EB2D">' +
            '<PARAM name="o3d_features" value="' + opt_features + '"/>' +
        '</OBJECT>';
    objElem = element.childNodes[0];
  } else {
    objElem = document.createElement('object');
    objElem.type = 'application/vnd.o3d.auto';
    objElem.style.width = '100%';
    objElem.style.height = '100%';
    objElem.setAttribute('o3d_features', opt_features);
    element.appendChild(objElem);
  }

  if (objElem.client.clientInfo.glsl) {
    o3djs.effect.setLanguage('glsl');
  }

  return objElem;
};

/**
 * Finds all divs with the an id that starts with "o3d" and inserts a client
 * area inside.
 *
 * NOTE: the size of the client area is always set to 100% which means the div
 * must have its size set or managed by the browser. Examples:
 *
 * -- A div of a specific size --
 * &lt;div id="o3d" style="width:800px; height:600px">&lt;/div>
 *
 * -- A div that fills its containing element --
 * &lt;div id="o3d" style="width:100%; height:100%">&lt;/div>
 *
 * In both cases, a DOCTYPE is probably required.
 *
 * You can also request certain features by adding the attribute
 * 'o3d_features' as in
 *
 * &lt;div id="o3d" o3d_features="FloatingPointTextures">&lt;/div>
 *
 * This allows you to specify different features per area. Otherwise you can
 * request features as an argument to this function.
 *
 * Normally this function handles failure for you but if you want to handle
 * failure in your own way you can supply a failure callback. Here is an example
 * of using this function with your own failure callback.
 *
 * <pre>
 * &lt;script type="text/javascript" id="o3dscript"&gt;
 * *
 * window.onload = init;
 *
 * function init() {
 *  o3djs.util.makeClients(onSuccess, '', undefined, onFailure);
 * }
 *
 * function onFailure(initStatus, error, id, tag) {
 *   // Get a list of the elements that would have had an O3D plugin object
 *   // inserted if it had succeed.
 *   var elements = o3djs.util.getO3DContainerElements(id, tag);
 *
 *   switch (initStatus) {
 *     case o3djs.util.rendererInitStatus.NO_PLUGIN:
 *       // Tell the user there is no plugin
 *       ...
 *       break;
 *     case o3djs.util.rendererInitStatus.OUT_OF_RESOURCES:
 *     case o3djs.util.rendererInitStatus.GPU_NOT_UP_TO_SPEC:,
 *     case o3djs.util.rendererInitStatus.INITIALIZATION_ERROR:
 *     default:
 *       // Tell the user there are other issues
 *       ...
 *       break;
 *   }
 * }
 *
 * function onSuccess(o3dElementsArray) {
 *   // Run your app.
 *   ...
 * }
 * &lt;/script&gt;
 * </pre>
 *
 * @param {!function(Array.<!Element>): void} callback Function to call when
 *     client objects have been created.
 * @param {string} opt_features A comma separated list of the
 *     features you need for your application. The current list of features:
 *
 *     <li>FloatingPointTextures: Includes the formats R32F, ABGR16F and
 *     ABGR32F</li>
 *     <li>LargeGeometry: Allows buffers to have more than 65534 elements.</li>
 *     <li>NotAntiAliased: Turns off anti-aliasing</li>
 *     <li>InitStatus=X: Where X is a number. Allows simulatation of the plugin
 *     failing</li>
 *
 *     The features are case sensitive.
 * @param {string} opt_requiredVersion version string in
 *     "major.minor.revision.build" format. You can leave out any
 *     non-important numbers for example "3" = require major version 3,
 *     "2.4" = require major version 2, minor version 4. If no string is
 *     passed in the version of the needed by this version of the javascript
 *     libraries will be created.
 * @param {!function(!o3d.Renderer.InitStatus, string, (string|undefined),
 *     (string|undefined)): void} opt_failureCallback Function to call if the
 *     plugin does not exist, if the required version is not installed, or if
 *     for some other reason the plugin can not start. If this function is not
 *     specified or is null the default behavior of leading the user to the
 *     download page will be provided. See o3djs.util.informPluginFailure for an
 *     example of this type of callback.
 * @param {string} opt_id The id to look for. This can be a regular
 *     expression. The default is "^o3d".
 * @param {string} opt_tag The type of tag to look for. The default is "div".
 * @see o3djs.util.informPluginFailure
 */
o3djs.util.makeClients = function(callback,
                                  opt_features,
                                  opt_requiredVersion,
                                  opt_failureCallback,
                                  opt_id,
                                  opt_tag) {
  opt_failureCallback = opt_failureCallback || o3djs.util.informPluginFailure;
  opt_requiredVersion = opt_requiredVersion || o3djs.util.REQUIRED_VERSION;
  if (!o3djs.util.requiredVersionAvailable(opt_requiredVersion)) {
    opt_failureCallback(o3djs.util.rendererInitStatus.NO_PLUGIN, '',
                        opt_id, opt_tag);
  } else {
    var clientElements = [];
    var elements = o3djs.util.getO3DContainerElements(opt_id, opt_tag);
    var mainClientElement = null;
    for (var ee = 0; ee < elements.length; ++ee) {
      var element = elements[ee];
      var features = opt_features;
      if (!features) {
        var o3d_features = element.getAttribute('o3d_features');
        if (o3d_features) {
          features = o3d_features;
        } else {
          features = '';
        }
      }

      var objElem = o3djs.util.createClient(element, features);
      clientElements.push(objElem);

      // If the callback is to be invoked in an embedded JavaScript engine,
      // one element must be identified with the id 'o3d'. This callback
      // will be invoked in the element identified as such.
      if (element.id === 'o3d') {
        mainClientElement = objElem;
      }
    }

    // Wait for the browser to initialize the clients.
    var clearId = window.setInterval(function() {
      var initStatus = 0;
      var error = '';
      var o3d;
      for (var cc = 0; cc < clientElements.length; ++cc) {
        var element = clientElements[cc];
        o3d = element.o3d;
        var ready = o3d &&
            element.client &&
            element.client.rendererInitStatus >
                o3djs.util.rendererInitStatus.UNINITIALIZED;
        if (!ready) {
          return;
        }
        var status = clientElements[cc].client.rendererInitStatus;
        // keep the highest status. This is the worst status.
        if (status > initStatus) {
          initStatus = status;
          error = clientElements[cc].client.lastError;
        }
      }

      window.clearInterval(clearId);

      // If the plugin could not initialize the graphics delete all of
      // the plugin objects
      if (initStatus > 0 && initStatus != o3d.Renderer.SUCCESS) {
        for (var cc = 0; cc < clientElements.length; ++cc) {
          var clientElement = clientElements[cc];
          clientElement.parentNode.removeChild(clientElement);
        }
        opt_failureCallback(initStatus, error, opt_id, opt_tag);
      } else {
        o3djs.base.snapshotProvidedNamespaces();

        // TODO: Is this needed with the new event code?
        for (var cc = 0; cc < clientElements.length; ++cc) {
          // Based on v8 support test, not on current engine, as V8
          // still needs to be initialized even with o3djs.util.Engine.BROWSER
          // on some configs.
          if (o3djs_isV8Supported())
            o3djs.base.initV8(clientElements[cc]);
          o3djs.event.startKeyboardEventSynthesis(clientElements[cc]);
          o3djs.error.setDefaultErrorHandler(clientElements[cc].client);
        }
        o3djs.base.init(clientElements[0]);

        switch (o3djs.util.mainEngine_) {
          case o3djs.util.Engine.BROWSER:
            callback(clientElements);
            break;
          case o3djs.util.Engine.V8:
            if (!mainClientElement) {
              throw 'V8 engine was requested but there is no element with' +
                  ' the id "o3d"';
            }

            // Retreive the code from the script tags and eval it in V8 to
            // duplicate the browser environment.
            var scriptTagText = o3djs.util.getScriptTagText_();
            mainClientElement.eval(scriptTagText);

            // Invoke the callback in V8.
            o3djs.util.callV8(mainClientElement,
                              callback,
                              o3djs.global,
                              [clientElements]);
            break;
          default:
            throw 'Unknown engine ' + o3djs.util.mainEngine_;
        }
      }
    }, 10);
  }
};

/*
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview This file contains utility functions for o3d running on
 * top of webgl.  The function o3djs.webgl.makeClients replaces the
 * function o3djs.util.makeClients.
 */

o3djs.provide('o3djs.webgl');

/**
 * A Module with various utilities.
 * @namespace
 */
o3djs.webgl = o3djs.webgl || {};


/**
 * Finds all divs with an id that starts with "o3d" and inits a canvas
 * under them with o3d client object and the o3d namespace.
 */
o3djs.webgl.makeClients = function(callback,
                                   opt_features,
                                   opt_requiredVersion,
                                   opt_failureCallback,
                                   opt_id,
                                   opt_tag,
                                   opt_debug) {
  opt_failureCallback = opt_failureCallback || o3djs.webgl.informPluginFailure;

  var clientElements = [];
  var elements = o3djs.util.getO3DContainerElements(opt_id, opt_tag);

  for (var ee = 0; ee < elements.length; ++ee) {
    var element = elements[ee];
    var features = opt_features;
    if (!features) {
      var o3d_features = element.getAttribute('o3d_features');
      if (o3d_features) {
        features = o3d_features;
      } else {
        features = '';
      }
    }
    var objElem = o3djs.webgl.createClient(element, features, opt_debug);
    if (!objElem) {
      // If we couldn't create the client then we don't call the callback.
      return;
    }
    clientElements.push(objElem);
  }

  // Wait for the client elements to be fully initialized. This
  // involves waiting for the page to fully layout and the initial
  // resize event to be processed.
  var clearId = window.setInterval(function() {
    for (var cc = 0; cc < clientElements.length; ++cc) {
      var element = clientElements[cc];
      if (!element.sizeInitialized_) {
        return;
      }
    }
    window.clearInterval(clearId);
    callback(clientElements);
  });
};


/**
 * Adds a wrapper object to single gl function context that checks for errors
 * before the call.
 * @param {WebGLContext} context
 * @param {string} fname The name of the function.
 * @return {}
 */
o3djs.webgl.createGLErrorWrapper = function(context, fname) {
    return function() {
        var rv = context[fname].apply(context, arguments);
        var err = context.getError();
        if (err != 0) {
            throw "GL error " + err + " in " + fname;
        }
        return rv;
    };
};


/**
 * Adds a wrapper object to a webgl context that checks for errors
 * before each function call.
 */
o3djs.webgl.addDebuggingWrapper = function(context) {
    // Thanks to Ilmari Heikkinen for the idea on how to implement this
    // so elegantly.
    var wrap = {};
    for (var i in context) {
      if (typeof context[i] == 'function') {
          wrap[i] = o3djs.webgl.createGLErrorWrapper(context, i);
      } else {
          wrap[i] = context[i];
      }
    }
    wrap.getError = function() {
        return context.getError();
    };
    return wrap;
};


/**
 * Inserts text indicating that a WebGL context could not be created under
 * the given node and links to the site about WebGL capable browsers.
 */
o3djs.webgl.webGlCanvasError = function(parentNode, unavailableElement) {
  var background = document.createElement('div');
  background.style.backgroundColor = '#ccffff';
  background.style.textAlign = 'center';
  background.style.margin = '10px';
  background.style.width = '100%';
  background.style.height = '100%';

  var messageHTML = '<br/><br/><a href="http://code.google.com/p/kuda/wiki/TroubleshootWebGL">' +
      'Please check that WebGL is enabled or click here to troubleshoot' +
      '</a><br/>';

  background.innerHTML = messageHTML;

  parentNode.appendChild(background);
};


/**
 * Creates a canvas under the given parent element and an o3d.Client
 * under that.
 *
 * @param {!Element} element The element under which to insert the client.
 * @param {string} opt_features Features to turn on.
 * @param {boolean} opt_debug Whether gl debugging features should be
 *     enabled.
 * @return {HTMLCanvas} The canvas element, or null if initializaton failed.
 */
o3djs.webgl.createClient = function(element, opt_features, opt_debug) {
  opt_features = opt_features || '';
  opt_debug = opt_debug || false;

  // If we're creating a webgl client, the assumption is we're using webgl,
  // in which case the only acceptable shader language is glsl.  So, here
  // we set the shader language to glsl.
  o3djs.effect.setLanguage('glsl');

  // Make the canvas automatically resize to fill the containing
  // element (div), and initialize its size correctly.
  var canvas;
  canvas = document.createElement('canvas');

  if (!canvas || !canvas.getContext) {
    o3djs.webgl.webGlCanvasError(element, 'HTMLCanvas');
    return null;
  }

  canvas.style.width = "100%";
  canvas.style.height = "100%";

  var client = new o3d.Client;

  var resizeHandler = function() {
    var width = Math.max(1, canvas.clientWidth);
    var height = Math.max(1, canvas.clientHeight);
    canvas.width = width;
    canvas.height = height;
    canvas.sizeInitialized_ = true;
    if (client.gl) {
      client.gl.displayInfo = {width: canvas.width, height: canvas.height};
    }
  };
  window.addEventListener('resize', resizeHandler, false);
  setTimeout(resizeHandler, 0);

  if (!client.initWithCanvas(canvas)) {
    o3djs.webgl.webGlCanvasError(element, 'WebGL context');
    return null;
  }

  // This keeps the cursor from changing to an I-beam when the user clicks and
  // drags.  It's easier on the eyes.
//  function returnFalse() {
//    return false;
//  }
//  document.onselectstart = returnFalse;
//  document.onmousedown = returnFalse;

  canvas.client = client;
  canvas.o3d = o3d;

  if (opt_debug) {
    client.gl = o3djs.webgl.addDebuggingWrapper(client.gl);
  }

  element.appendChild(canvas);
  return canvas;
};


/*
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview This file contains various functions to help debug for o3d
 * applications.
 *
 *     Note: This library is only a sample. It is not meant to be some official
 *     library. It is provided only as example code.
 *
 */

o3djs.provide('o3djs.debug');

var O3D_DEBUG_PREFIX = 'o3dDebug_';
var O3D_DEBUG_PREFIX_LENGTH = O3D_DEBUG_PREFIX.length;
var O3D_DEBUG_COLOR_PARAM_NAME = O3D_DEBUG_PREFIX + 'Color';
var O3D_DEBUG_VECTOR_SCALE_PARAM_NAME =
    O3D_DEBUG_PREFIX + 'VectorScale';
var O3D_DEBUG_AXIS_SHAPE_NAME = O3D_DEBUG_PREFIX + 'AxisShape';
var O3D_DEBUG_LINE_SHAPE_NAME = O3D_DEBUG_PREFIX + 'LineShape';
var O3D_DEBUG_SPHERE_SHAPE_NAME = O3D_DEBUG_PREFIX + 'SphereShape';
var O3D_DEBUG_CUBE_SHAPE_NAME = O3D_DEBUG_PREFIX + 'CubeShape';

var O3D_DEBUG_AXIS_INFO_ = [
    {offset: [1, 0, 0], color: [1, 0, 0, 1]},
    {offset: [0, 1, 0], color: [0, 1, 0, 1]},
    {offset: [0, 0, 1], color: [0, 0, 1, 1]}];

/**
 * Checks whether or not a transform is a debug transform.
 * @param {!o3d.Transform} transform Transform to check.
 * @return {boolean} true if this transform is a debug transform.
 */
o3djs.debug.isDebugTransform = function(transform) {
  var name = transform.name;
  var isDT =
    name.length >= O3D_DEBUG_PREFIX_LENGTH &&
    name.substr(0, O3D_DEBUG_PREFIX_LENGTH) == O3D_DEBUG_PREFIX;
  return isDT;
};

/**
 * Gets the debug transform.
 * @private
 * @param {!o3d.Transform} transform Transform to get debug Transform
 *     from.
 * @param {string} name Name of debug transform to get.
 * @return {o3d.Transform} Debug Transform or null if not found.
 */
o3djs.debug.getDebugTransform_ = function(transform, name) {
  if (transform.name == name) {
    return transform;
  } else {
    var children = transform.children;
    for (var cc = 0; cc < children.length; ++cc) {
      if (children[cc].name == name) {
        return children[cc];
      }
    }
  }
  return null;
};

/**
 * Creates shaders that output the constant color from a parameter.
 * @private
 * @param {string} colorParamName Name of color parameter to use.
 * @return {string} Shader string.
 */
o3djs.debug.createColorShaders_ = function(colorParamName) {
  var p = o3djs.effect;
  var shaders =
    'uniform ' + p.MATRIX4 + ' worldViewProjection' +
        p.semanticSuffix('WORLDVIEWPROJECTION') + ';\n' +
    p.BEGIN_IN_STRUCT +
      p.ATTRIBUTE + p.FLOAT4 +  ' position' +
          p.semanticSuffix('POSITION') + ';\n' +
    p.END_STRUCT +
    p.BEGIN_OUT_STRUCT +
      p.VARYING + p.FLOAT4 + ' ' + p.VARYING_DECLARATION_PREFIX + 'position' +
          p.semanticSuffix('POSITION') + ';\n' +
    p.END_STRUCT +
    p.beginVertexShaderMain() +
    '  ' + p.VERTEX_VARYING_PREFIX + 'position = ' +
        p.mul(p.ATTRIBUTE_PREFIX + 'position', 'worldViewProjection')
        + ';\n' +
    p.endVertexShaderMain() +
    p.pixelShaderHeader() +
    'uniform ' + p.FLOAT4 + ' ' + colorParamName + ';\n' +
    p.beginPixelShaderMain() +
    p.endPixelShaderMain(colorParamName) +
    p.entryPoints() +
    p.matrixLoadOrder();
    return shaders;
};

/**
 * Creates shaders that output the constant color from a parameter and scale
 * the vertices in object space.
 * @private
 * @param {string} colorParamName Name of color parameter to use.
 * @param {string} scaleParamName Name of scale parameter to use.
 * @return {string} Shader string.
 */
o3djs.debug.createScaleShaders_ = function(colorParamName, scaleParamName) {
  var p = o3djs.effect;
  var shaders =
  'uniform ' + p.FLOAT3 + ' ' + scaleParamName + ';\n' +
  'uniform ' + p.MATRIX4 + ' worldViewProjection' +
      p.semanticSuffix('WORLDVIEWPROJECTION') + ';\n' +
  p.BEGIN_IN_STRUCT +
    p.ATTRIBUTE + p.FLOAT4 + ' position' + p.semanticSuffix('POSITION') +
        ';\n' +
  p.END_STRUCT +
  p.BEGIN_OUT_STRUCT +
    p.VARYING + p.FLOAT4 + ' ' + p.VARYING_DECLARATION_PREFIX + 'position' +
        p.semanticSuffix('POSITION') + ';\n' +
  p.END_STRUCT +
  p.beginVertexShaderMain() +
  '  ' + p.FLOAT4 + ' position = ' + p.FLOAT4 + '(\n' +
  '    ' + p.ATTRIBUTE_PREFIX + 'position.x * ' + scaleParamName + '.x,\n' +
  '    ' + p.ATTRIBUTE_PREFIX + 'position.y * ' + scaleParamName + '.y,\n' +
  '    ' + p.ATTRIBUTE_PREFIX + 'position.z * ' + scaleParamName + '.z,\n' +
  '    1);\n' +
  '  ' + p.VERTEX_VARYING_PREFIX + 'position = ' +
      p.mul('position', 'worldViewProjection') + ';\n' +
  p.endVertexShaderMain() +
  p.pixelShaderHeader() +
  'uniform ' + p.FLOAT4 + ' ' + colorParamName + ';\n' +
  p.beginPixelShaderMain() +
  p.endPixelShaderMain(colorParamName) +
  p.entryPoints() +
  p.matrixLoadOrder();
  return shaders;
};


/**
 * Defines a namespace for o3djs.debug.
 * @namespace
 */
o3djs.debug = o3djs.debug || {};

/**
 * An object to manage a single debug line.
 * @constructor
 * @param {!o3djs.debug.DebugLineGroup} debugLineGroup DebugLineGroup
 *     this line belongs too.
 */
o3djs.debug.DebugLine = function(debugLineGroup) {
  /**
   * The DebugLineGroup this DebugLine is managed by.
   * @private
   * @type {!o3djs.debug.DebugLineGroup}
   */
  this.debugLineGroup_ = debugLineGroup;
  var pack = debugLineGroup.getPack();

  /**
   * The transform for this DebugLine.
   * @private
   * @type {!o3d.Transform}
   */
  this.transform_ = pack.createObject('Transform');
  this.transform_.name = O3D_DEBUG_LINE_SHAPE_NAME;
  this.transform_.addShape(debugLineGroup.getLineShape());

  /**
   * The start position of the line.
   * @private
   * @type {!o3djs.math.Vector3}
   */
  this.start_ = [0, 0, 0];

  /**
   * The start position of the line.
   * @private
   * @type {!o3djs.math.Vector3}
   */
  this.end_ = [0, 0, 0];

  /**
   * The color param for the line.
   * @private
   * @type {!o3d.ParamFloat4}
   */
  this.colorParam_ = this.transform_.createParam(
      O3D_DEBUG_COLOR_PARAM_NAME, 'ParamFloat4');
  this.colorParam_.value = debugLineGroup.getColor();
};

/**
 * Destroys this line object cleaning up the resources used by it.
 */
o3djs.debug.DebugLine.prototype.destroy = function() {
  this.transform_.parent = null;
  this.debugLineGroup_.getPack().removeObject(this.transform_);
};

/**
 * Returns a unique Id for the line.
 * @return {number} the Id of the line.
 */
o3djs.debug.DebugLine.prototype.getId = function() {
  return this.transform_.clientId;
};

/**
 * Updates the line with the current start and end settings.
 * @private
 */
o3djs.debug.DebugLine.prototype.update_ = function() {
  var math = o3djs.math;
  var vector = math.subVector(this.end_, this.start_);
  var direction = math.normalize(vector);
  var dot = math.dot(direction, [0, 1, 0]);
  var perp1;
  var perp2;
  if (dot > 0.99) {
    perp2 = math.cross([1, 0, 0], direction);
    perp1 = math.cross(perp2, direction);
  } else {
    perp1 = math.cross([0, 1, 0], direction);
    perp2 = math.cross(perp1, direction);
  }
  this.transform_.localMatrix =
      [perp2.concat(0),
       direction.concat(0),
       perp1.concat(0),
       this.start_.concat(1)];
  this.transform_.scale(1, math.length(vector), 1);
};

/**
 * Sets the end points of the DebugLine.
 * @param {!o3djs.math.Vector3} start Start point for line.
 * @param {!o3djs.math.Vector3} end End point for line.
 */
o3djs.debug.DebugLine.prototype.setEndPoints = function(start, end) {
  this.start_ = start;
  this.end_ = end;
  this.update_();
};

/**
 * Sets the start point of the DebugLine.
 * @param {!o3djs.math.Vector3} start Start point for line.
 */
 o3djs.debug.DebugLine.prototype.setStart = function(start) {
  this.start_ = start;
  this.update_();
};

/**
 * Sets the end point of the DebugLine.
 * @param {!o3djs.math.Vector3} end End point for line.
 */
o3djs.debug.DebugLine.prototype.setEnd = function(end) {
  this.end_ = end;
  this.update_();
};

/**
 * Sets the color of the DebugLine.
 * @param {!o3djs.math.Vector4} color The color of the debug line.
 */
o3djs.debug.DebugLine.prototype.setColor = function(color) {
  this.colorParam_.value = color;
};

/**
 * Sets the visibility of the DebugLine.
 * @param {boolean} visible True = visible.
 */
o3djs.debug.DebugLine.prototype.setVisible = function(visible) {
  this.transform_.parent = visible ? this.debugLineGroup_.getRoot() : null;
};

/**
 * Removes this line.
 */
o3djs.debug.DebugLine.prototype.remove = function() {
  this.transform_.parent = null;
  this.debugLineGroup_.remove(this);
};

/**
 * An object to manage debug lines.
 * @constructor
 * @param {!o3djs.debug.DebugHelper} debugHelper The DebugHelper
 *     associated with this object.
 * @param {!o3d.Transform} root Transform to put debug lines under.
 */
o3djs.debug.DebugLineGroup = function(debugHelper, root) {
  /**
   * The default color to make new lines.
   * @private
   * @type {!o3djs.math.Vector4}
   */
  this.currentColor_ = [1, 1, 1, 1];

  /**
   * The lines in this group indexed by clientId.
   * @private
   * @type {!Object.<number, !o3djs.debug.DebugLine>}
   */
  this.lineTransforms_ = { };

  /**
   * The unused lines in this group indexed by clientId.
   * @private
   * @type {!Object.<number, !o3djs.debug.DebugLine>}
   */
  this.freeLineTransforms_ = { };

  /**
   * The DebugHelper managing this DebugLineGroup.
   * @private
   * @type {!o3djs.debug.DebugHelper}
   */
  this.debugHelper_ = debugHelper;

  /**
   * The root transform for lines in this group.
   * @private
   * @type {!o3d.Transform}
   */
  this.root_ = root;
};

/**
 * Gets the root transform for this line group.
 * @return {!o3d.Transform} The root transform for this line group.
 */
o3djs.debug.DebugLineGroup.prototype.getRoot = function() {
  return this.root_;
};

/**
 * Gets the pack for this line group.
 * @return {!o3d.Pack} The pack for this line group.
 */
o3djs.debug.DebugLineGroup.prototype.getPack = function() {
  return this.debugHelper_.getPack();
};

/**
 * Gets the shape for lines.
 * @return {!o3d.Shape} The shape for lines.
 */
o3djs.debug.DebugLineGroup.prototype.getLineShape = function() {
  return this.debugHelper_.getLineShape();
};

/**
 * Gets the current color for this line group.
 * @return {!o3djs.math.Vector4} The current color.
 */
o3djs.debug.DebugLineGroup.prototype.getColor = function() {
  return this.currentColor_;
};

/**
 * Sets the current color for this line group.  All lines added after
 * setting this will be this color by default.
 * @param {!o3djs.math.Vector4} color The color for this line group.
 */
o3djs.debug.DebugLineGroup.prototype.setColor = function(color) {
  this.currentColor_ = color;
};

/**
 * Gets a debug line. If none exist creates a new one.
 * @private
 * @return {!o3djs.debug.DebugLine} The DebugLine.
 */
o3djs.debug.DebugLineGroup.prototype.getLine_ = function() {
  for (var sid in this.freeLineTransforms_) {
    var id = /** @type {number} */ (sid);
    var line = this.freeLineTransforms_[id];
    delete this.freeLineTransforms_[id];
    return line;
  }
  return new o3djs.debug.DebugLine(this);
};

/**
 * Adds a debug line.
 * @param {!o3djs.math.Vector3} opt_start Start position for line.
 * @param {!o3djs.math.Vector3} opt_end End position for line.
 * @param {!o3djs.math.Vector4} opt_color Color for line.
 * @return {!o3djs.debug.DebugLine} The DebugLine.
 */
o3djs.debug.DebugLineGroup.prototype.addLine = function(opt_start,
                                                        opt_end,
                                                        opt_color) {
  var line = this.getLine_();
  line.setEndPoints(opt_start || [0, 0, 0], opt_end || [0, 0, 0]);
  line.setColor(opt_color || this.currentColor_);
  line.setVisible(true);
  this.lineTransforms_[line.getId()] = line;
  return line;
};

/**
 * Clears all the lines in this group.
 */
o3djs.debug.DebugLineGroup.prototype.clear = function() {
  for (var sid in this.lineTransforms_) {
    var id = /** @type {number} */ (sid);
    var line = this.lineTransforms_[id];
    line.setVisible(false);
    this.freeLineTransforms_[id] = line;
  }
  this.lineTransforms_ = { };
};

/**
 * Destroys a DeubgLineGroup, freeing all its lines and resources.
 */
o3djs.debug.DebugLineGroup.prototype.destroy = function() {
  this.clear();
  for (var sid in this.freeLineTransforms_) {
    var id = /** @type {number} */ (sid);
    this.freeLineTransforms_[id].destroy();
  }
  this.freeLineTransforms_ = { };
};

/**
 * Removes a line.
 * @param {!o3djs.debug.DebugLine} line Line to remove.
 */
o3djs.debug.DebugLineGroup.prototype.remove = function(line) {
  var id = line.getId();
  delete this.lineTransforms_[id];
  this.freeLineTransforms_[id] = line;
};

/**
 * A Debug object to help with debugging o3d apps.
 *
 * A debug helper object provides functions to help debug your o3d
 * application and manages the resources needed to do that for you. For
 * example it can add axes, spheres and boxes to your transforms as well as
 * draw lines in 3d space given 2 points.
 *
 * @constructor
 * @param {!o3d.Pack} pack Pack for this debug object to use to manage
 *     its resources.
 * @param {!o3djs.rendergraph.ViewInfo} viewInfo ViewInfo for debug
 *     visuals.
 */
o3djs.debug.DebugHelper = function(pack, viewInfo) {
  this.pack_ = pack;
  this.viewInfo_ = viewInfo;
  this.axisPrimitives_ = [];
  this.axisShape_ = pack.createObject('Shape');
  this.axisShape_.name = O3D_DEBUG_AXIS_SHAPE_NAME;
  this.lineShape_ = pack.createObject('Shape');
  this.lineShape_.name = O3D_DEBUG_LINE_SHAPE_NAME;

  // Setup shape, material, primitive for axes.
  {
    // create a simple material for the axis.
    var effect = pack.createObject('Effect');
    var shaders = o3djs.debug.createScaleShaders_(
        O3D_DEBUG_COLOR_PARAM_NAME,
        O3D_DEBUG_VECTOR_SCALE_PARAM_NAME);
    effect.loadFromFXString(shaders);
    var material = pack.createObject('Material');
    material.effect = effect;
    material.drawList = viewInfo.performanceDrawList;
    effect.createUniformParameters(material);

    // Set the default color to white.
    material.getParam(O3D_DEBUG_COLOR_PARAM_NAME).value = [1, 1, 1, 1];

    // Set the default scale.
    material.getParam(O3D_DEBUG_VECTOR_SCALE_PARAM_NAME).value =
        [1, 1, 1];

    // Create the axis shape.
    for (var ii = 0; ii < O3D_DEBUG_AXIS_INFO_.length; ++ii) {
      var info = O3D_DEBUG_AXIS_INFO_[ii];
      var cubeShape = o3djs.primitives.createCube(pack,
                                                  material,
                                                  1,
                                                  [[1, 0, 0, 0],
                                                   [0, 1, 0, 0],
                                                   [0, 0, 1, 0],
                                                   [info.offset[0] * 0.5,
                                                    info.offset[1] * 0.5,
                                                    info.offset[2] * 0.5,
                                                    1]]);
      var cube = cubeShape.elements[0];
      cube.owner = this.axisShape_;
      pack.removeObject(cubeShape);
      cube.createParam(O3D_DEBUG_COLOR_PARAM_NAME, 'ParamFloat4').value =
          info.color;
      cube.createParam(O3D_DEBUG_VECTOR_SCALE_PARAM_NAME, 'ParamFloat3');
      this.axisPrimitives_[ii] = cube;
    }

    this.axisMaterial_ = material;
    this.setAxisScale(10, 1);
  }

  // Setup shape, material, primitive for debug lines.
  {
    // create a simple material for the debug lines.
    var effect = pack.createObject('Effect');
    var shaders = o3djs.debug.createColorShaders_(O3D_DEBUG_COLOR_PARAM_NAME);
    effect.loadFromFXString(shaders);
    var material = pack.createObject('Material');
    material.effect = effect;
    material.drawList = viewInfo.performanceDrawList;
    effect.createUniformParameters(material);

    // Set the default color to white.
    material.getParam(O3D_DEBUG_COLOR_PARAM_NAME).value = [1, 1, 1, 1];

    // Create the debug line shape.
    var vertices = [0, 0, 0, 0, 1, 0];
    var streamBank = pack.createObject('StreamBank');
    var primitive = pack.createObject('Primitive');
    var shape = pack.createObject('Shape');
    var vertexBuffer = pack.createObject('VertexBuffer');
    var positionField = vertexBuffer.createField('FloatField', 3);
    vertexBuffer.set(vertices);
    primitive.owner = shape;
    primitive.createDrawElement(pack, null);
    primitive.streamBank = streamBank;
    primitive.material = material;
    primitive.numberVertices = 2;
    primitive.numberPrimitives = 1;
    primitive.primitiveType = o3djs.base.o3d.Primitive.LINELIST;
    streamBank.setVertexStream(o3djs.base.o3d.Stream.POSITION,
                               0,
                               positionField,
                               0);
    this.lineShape_ = shape;
    this.lineShape_.name = O3D_DEBUG_LINE_SHAPE_NAME;
    this.lineMaterial_ = material;
  }

  {
    this.sphereShape_ = o3djs.lineprimitives.createLineSphere(
        pack,
        this.axisMaterial_,
        0.5, 8, 8);
    this.sphereShape_.name = O3D_DEBUG_SPHERE_SHAPE_NAME;
    var primitive = this.sphereShape_.elements[0];
    this.sphereScaleParam_ = primitive.createParam(
        O3D_DEBUG_VECTOR_SCALE_PARAM_NAME,
        'ParamFloat3').value = [1, 1, 1];
  }

  {
    this.cubeShape_ = o3djs.lineprimitives.createLineCube(
        pack,
        this.axisMaterial_,
        1);
    this.cubeShape_.name = O3D_DEBUG_CUBE_SHAPE_NAME;
    var primitive = this.cubeShape_.elements[0];
    this.cubeScaleParam_ = primitive.createParam(
        O3D_DEBUG_VECTOR_SCALE_PARAM_NAME,
        'ParamFloat3').value = [1, 1, 1];
  }
};


/**
 * Gets the pack for this DebugHelper.
 * @return {!o3d.Pack} The pack for this DebugHelper.
 */
o3djs.debug.DebugHelper.prototype.getPack = function() {
  return this.pack_;
};

/**
 * Gets the line shape.
 * @return {!o3d.Shape} The shape for debug lines.
 */
o3djs.debug.DebugHelper.prototype.getLineShape = function() {
  return this.lineShape_;
};

/**
 * Sets the length and width of the axis lines.
 * @param {number} length Length of an axis in the direction of the axis.
 * @param {number} width Width of the axis or its thickness.
 */
o3djs.debug.DebugHelper.prototype.setAxisScale = function(length,
                                                          width) {
  for (var ii = 0; ii < O3D_DEBUG_AXIS_INFO_.length; ++ii) {
    var info = O3D_DEBUG_AXIS_INFO_[ii];
    this.axisPrimitives_[ii].getParam(
        O3D_DEBUG_VECTOR_SCALE_PARAM_NAME).value = [
            info.offset[0] ? length : width,
            info.offset[1] ? length : width,
            info.offset[2] ? length : width];
  }
};

/**
 * Creates a debug shape at a world position.
 * @private
 * @param {!o3djs.math.Vector3} position Position at which to create shape.
 * @param {!o3d.Shape} shape Shape to add to transform.
 * @return {!o3d.Transform} transform for shape.
 */
o3djs.debug.DebugHelper.prototype.createShape_ = function(position, shape) {

  var debugTransform = this.getPack().createObject('Transform');
  debugTransform.name = shape.name;
  debugTransform.addShape(shape);
  debugTransform.parent = this.viewInfo_.treeRoot;
  debugTransform.translate(position);
  return debugTransform;
};

/**
 * Adds an debug shape to a transform.
 * @private
 * @param {!o3d.Transform} transform Transform to add shape to.
 * @param {!o3d.Shape} shape Shape to add to transform.
 */
o3djs.debug.DebugHelper.prototype.addShape_ = function(transform,
                                                       shape) {

  var debugTransform = o3djs.debug.getDebugTransform_(transform, shape.name);
  if (!debugTransform) {
    var debugTransform = this.getPack().createObject('Transform');
    debugTransform.name = shape.name;
    debugTransform.addShape(shape);
    debugTransform.parent = transform;
  }
};

/**
 * Removes a debug shape from a transform
 * @private
 * @param {!o3d.Transform} transform Transform to remove shape from.
 * @param {!o3d.Shape} shape Shape to remove from transform.
 */
o3djs.debug.DebugHelper.prototype.removeShape_ = function(transform,
                                                          shape) {
  var name = shape.name;
  var debugTransform = o3djs.debug.getDebugTransform_(transform, shape.name);
  if (debugTransform) {
    debugTransform.parent = null;
    this.getPack().removeObject(debugTransform);
  }
};

/**
 * Adds a debug shape to all transform a tree.
 * @private
 * @param {!o3d.Transform} treeRoot root of tree to add shape to.
 * @param {!o3d.Shape} shape Shape to add to transforms.
 */
o3djs.debug.DebugHelper.prototype.addShapes_ = function(treeRoot,
                                                        shape) {
  this.addShape_(treeRoot, shape);
  var children = treeRoot.children;
  for (var cc = 0; cc < children.length; ++cc) {
    var child = children[cc];
    if (!o3djs.debug.isDebugTransform(child)) {
      this.addShapes_(child, shape);
    }
  }
};

/**
 * Removes a debug shape from all transforms in a tree.
 * @private
 * @param {!o3d.Transform} treeRoot root of tree to remove axes from.
 * @param {!o3d.Shape} shape Shape to remove from transforms.
 */
o3djs.debug.DebugHelper.prototype.removeShapes_ = function(treeRoot,
                                                           shape) {
  this.removeShape_(treeRoot, shape);
  var children = treeRoot.children;
  for (var cc = 0; cc < children.length; ++cc) {
    var child = children[cc];
    if (!o3djs.debug.isDebugTransform(child)) {
      this.removeShapes_(child, shape);
    }
  }
};

/**
 * Sets a param value on a debug transform. If the param does not exist it
 * will be created.
 * @private
 * @param {!o3d.Transform} transform Transform on which debug transform
 *     exists.
 * @param {string} name Name of debug transform.
 * @param {string} paramName Name of param to set.
 * @param {string} paramType type of param to set.
 * @param {*} paramValue value to set param.
 */
o3djs.debug.DebugHelper.prototype.addSetDebugTransformParam_ = function(
    transform,
    name,
    paramName,
    paramType,
    paramValue) {
  var debugTransform = o3djs.debug.getDebugTransform_(transform, name);
  if (debugTransform) {
    var param = debugTransform.getParam(paramName);
    if (!param) {
      param = debugTransform.createParam(paramName, paramType);
    }
    param.value = paramValue;
  }
};

/**
 * Adds an axis to a transform.
 * @param {!o3d.Transform} transform Transform to add axis to.
 */
o3djs.debug.DebugHelper.prototype.addAxis = function(transform) {
  this.addShape_(transform, this.axisShape_);
};

/**
 * Removes an axis from a transform
 * @param {!o3d.Transform} transform Transform to remove axis from.
 */
o3djs.debug.DebugHelper.prototype.removeAxis = function(transform) {
  this.removeShape_(transform, this.axisShape_);
};

/**
 * Adds axes to all transform in a tree.
 * @param {!o3d.Transform} treeRoot root of tree to add axes to.
 */
o3djs.debug.DebugHelper.prototype.addAxes = function(treeRoot) {
  this.addShapes_(treeRoot, this.axisShape_);
};

/**
 * Removes axes from all transforms in a tree.
 * @param {!o3d.Transform} treeRoot root of tree to remove axes from.
 */
o3djs.debug.DebugHelper.prototype.removeAxes = function(treeRoot) {
  this.removeShapes_(treeRoot, this.axisShape_);
};

/**
 * Set axis color.
 * @param {!o3d.Transform} transform Transform on which to change axis
 *     color.
 * @param {!o3djs.math.Vector4} color 4 number array in RGBA format.
 */
o3djs.debug.DebugHelper.prototype.setAxisColor = function(transform,
                                                          color) {
  this.addSetDebugTransformParam_(transform,
                                  O3D_DEBUG_AXIS_SHAPE_NAME,
                                  O3D_DEBUG_COLOR_PARAM_NAME,
                                  'ParamFloat4',
                                  color);
};

/**
 * Removes the color from an axis.
 * @param {!o3d.Transform} transform Transform on which to remove color.
 */
o3djs.debug.DebugHelper.prototype.clearAxisColor = function(transform) {
  var debugTransform = o3djs.debug.getDebugTransform_(
      transform,
      O3D_DEBUG_AXIS_SHAPE_NAME);
  if (debugTransform) {
    var colorParam = debugTransform.getParam(O3D_DEBUG_COLOR_PARAM_NAME);
    if (colorParam) {
      debugTransform.removeParam(colorParam);
    }
  }
};

/**
 * Creates a sphere in world space.
 * @param {!o3djs.math.Vector3} position Position at which to create sphere.
 * @param {!o3djs.math.Vector4} opt_color RGBA color for sphere.
 * @param {number} opt_scale of sphere.
 * @return {!o3d.Transform} transform for sphere.
 */
o3djs.debug.DebugHelper.prototype.createSphere = function(position,
                                                          opt_color,
                                                          opt_scale) {
  var transform = this.createShape_(position, this.sphereShape_);
  if (opt_color) {
    this.setSphereColor(transform, opt_color);
  }
  if (opt_scale) {
    this.setSphereScale(transform, opt_scale);
  }
  return transform;
};

/**
 * Adds a sphere to a transform.
 * @param {!o3d.Transform} transform Transform to add sphere to.
 * @param {!o3djs.math.Vector4} opt_color RGBA color for sphere.
 * @param {number} opt_scale of sphere.
 */
o3djs.debug.DebugHelper.prototype.addSphere = function(transform,
                                                       opt_color,
                                                       opt_scale) {
  this.addShape_(transform, this.sphereShape_);
  if (opt_color) {
    this.setSphereColor(transform, opt_color);
  }
  if (opt_scale) {
    this.setSphereScale(transform, opt_scale);
  }
};

/**
 * Removes a sphere from a transform
 * @param {!o3d.Transform} transform Transform to remove sphere from.
 */
o3djs.debug.DebugHelper.prototype.removeSphere = function(transform) {
  this.removeShape_(transform, this.sphereShape_);
};

/**
 * Adds spheres to all transform a tree.
 * @param {!o3d.Transform} treeRoot root of tree to add spheres to.
 */
o3djs.debug.DebugHelper.prototype.addSpheres = function(treeRoot) {
  this.addShapes_(treeRoot, this.sphereShape_);
};

/**
 * Removes spheres from all transforms in a tree.
 * @param {!o3d.Transform} treeRoot root of tree to remove spheres from.
 */
o3djs.debug.DebugHelper.prototype.removeSpheres = function(treeRoot) {
  this.removeShapes_(treeRoot, this.sphereShape_);
};

/**
 * Set sphere color.
 * @param {!o3d.Transform} transform Transform on which to change sphere
 *     color.
 * @param {!o3djs.math.Vector4} color 4 number array in RGBA format.
 */
o3djs.debug.DebugHelper.prototype.setSphereColor = function(transform,
                                                            color) {
  this.addSetDebugTransformParam_(transform,
                                  O3D_DEBUG_SPHERE_SHAPE_NAME,
                                  O3D_DEBUG_COLOR_PARAM_NAME,
                                  'ParamFloat4',
                                  color);
};

/**
 * Sets the scale of a debug sphere.
 * @param {!o3d.Transform} transform Transform on which to change sphere
 *     scale.
 * @param {number} scale Scale to make the sphere.
 */
o3djs.debug.DebugHelper.prototype.setSphereScale = function(transform,
                                                            scale) {
  this.addSetDebugTransformParam_(transform,
                                  O3D_DEBUG_SPHERE_SHAPE_NAME,
                                  O3D_DEBUG_VECTOR_SCALE_PARAM_NAME,
                                  'ParamFloat3',
                                  [scale, scale, scale]);
};

/**
 * Creates a cube at a world position..
 * @param {!o3djs.math.Vector3} position Position at which to create sphere.
 * @param {!o3djs.math.Vector4} opt_color RGBA color for cube.
 * @param {number} opt_scale of cube.
 * @return {!o3d.Transform} transform for cube.
 */
o3djs.debug.DebugHelper.prototype.createCube = function(position,
                                                        opt_color,
                                                        opt_scale) {
  var transform = this.createShape_(position, this.cubeShape_);
  if (opt_color) {
    this.setCubeColor(transform, opt_color);
  }
  if (opt_scale) {
    this.setCubeScale(transform, opt_scale);
  }
  return transform;
};

/**
 * Adds a cube to a transform.
 * @param {!o3d.Transform} transform Transform to add cube to.
 * @param {!o3djs.math.Vector4} opt_color RGBA color for cube.
 * @param {number} opt_scale of cube.
 */
o3djs.debug.DebugHelper.prototype.addCube = function(transform,
                                                     opt_color,
                                                     opt_scale) {
  this.addShape_(transform, this.cubeShape_);
  if (opt_color) {
    this.setCubeColor(transform, opt_color);
  }
  if (opt_scale) {
    this.setCubeScale(transform, opt_scale);
  }
};

/**
 * Removes a cube from a transform
 * @param {!o3d.Transform} transform Transform to remove cube from.
 */
o3djs.debug.DebugHelper.prototype.removeCube = function(transform) {
  this.removeShape_(transform, this.cubeShape_);
};

/**
 * Adds cubes to all transform in a tree.
 * @param {!o3d.Transform} treeRoot root of tree to add cubes to.
 */
o3djs.debug.DebugHelper.prototype.addCubes = function(treeRoot) {
  this.addShapes_(treeRoot, this.cubeShape_);
};

/**
 * Removes cubes from all transforms in a tree.
 * @param {!o3d.Transform} treeRoot root of tree to remove cubes from.
 */
o3djs.debug.DebugHelper.prototype.removeCubes = function(treeRoot) {
  this.removeShapes_(treeRoot, this.cubeShape_);
};

/**
 * Set cube color.
 * @param {!o3d.Transform} transform Transform on which to change cube
 *     color.
 * @param {!o3djs.math.Vector3} color 4 number array in RGBA format.
 */
o3djs.debug.DebugHelper.prototype.setCubeColor = function(transform,
                                                          color) {
  this.addSetDebugTransformParam_(transform,
                                  O3D_DEBUG_CUBE_SHAPE_NAME,
                                  O3D_DEBUG_COLOR_PARAM_NAME,
                                  'ParamFloat4',
                                  color);
};

/**
 * Sets the scale of a cubes.
 * @param {!o3d.Transform} transform Transform on which to change cube
 *     scale.
 * @param {number} scale Scale to make the cube.
 */
o3djs.debug.DebugHelper.prototype.setCubeScale = function(transform,
                                                          scale) {
  this.addSetDebugTransformParam_(transform,
                                  O3D_DEBUG_CUBE_SHAPE_NAME,
                                  O3D_DEBUG_VECTOR_SCALE_PARAM_NAME,
                                  'ParamFloat3',
                                  [scale, scale, scale]);
};

/**
 * Creates a debug line group. A Debug line group's purpose is the let you
 * quickly delete a set of lines.
 * @param {!o3d.Transform} root Root transform to use for lines.
 * @return {!o3djs.debug.DebugLineGroup} The debug line group.
 */
o3djs.debug.DebugHelper.prototype.createDebugLineGroup =
    function(root) {
  return new o3djs.debug.DebugLineGroup(this, root);
};

/**
 * Creates a debug helper object.
 *
 * A debug helper object provides functions to help debug your o3d
 * application and manages the resources needed to do that for you. For
 * example it can add axes, spheres and boxes to your transforms as well as
 * draw lines in 3d space given 2 points.
 *
 * @param {!o3d.Pack} pack Pack for DebugHelper to manage its resources
 *      with.
 * @param {!o3djs.rendergraph.ViewInfo} viewInfo ViewInfo for debug
 *     visuals.
 * @return {!o3djs.debug.DebugHelper} the DebugHelper object.
 */
o3djs.debug.createDebugHelper = function(pack, viewInfo) {
  return new o3djs.debug.DebugHelper(pack, viewInfo);
};

/*
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview This file contains various functions for helping setup
 * elements for o3d
 */

o3djs.provide('o3djs.element');

/**
 * A Module for element functions.
 * @namespace
 */
o3djs.element = o3djs.element || {};

/**
 * Sets the bounding box and z sort point of an element.
 * @param {!o3d.Element} element Element to set bounding box and z sort point
 *     on.
 */
o3djs.element.setBoundingBoxAndZSortPoint = function(element) {
  var boundingBox = element.getBoundingBox(0);
  var minExtent = boundingBox.minExtent;
  var maxExtent = boundingBox.maxExtent;
  element.boundingBox = boundingBox;
  element.cull = true;
  element.zSortPoint = o3djs.math.divVectorScalar(
      o3djs.math.addVector(minExtent, maxExtent), 2);
};

/**
 * Adds missing texture coordinate streams to a primitive.
 *
 * This is very application specific but if it's a primitive
 * and if it uses a collada material the material builder
 * assumes 1 TEXCOORD stream per texture. In other words if you have
 * both a specular texture AND a diffuse texture the builder assumes
 * you have 2 TEXCOORD streams. This assumption is often false.
 *
 * To work around this we check how many streams the material
 * expects and if there are not enough UVs streams we duplicate the
 * last TEXCOORD stream until there are, making a BIG assumption that
 * that will work.
 *
 * The problem is maybe you have 4 textures and each of them share
 * texture coordinates. There is information in the collada file about
 * what stream to connect each texture to.
 *
 * @param {!o3d.Element} element Element to add streams to.
 */
o3djs.element.addMissingTexCoordStreams = function(element) {
  // TODO: We should store that info. The conditioner should either
  // make streams that way or pass on the info so we can do it here.
  if (element.isAClassName('o3d.Primitive')) {
    var material = /** @type {!o3d.Material} */ (element.material);
    var streamBank = element.streamBank;
    var lightingType = o3djs.effect.getColladaLightingType(material);
    if (lightingType) {
      var numTexCoordStreamsNeeded =
          o3djs.effect.getNumTexCoordStreamsNeeded(material);
      // Count the number of TEXCOORD streams the streamBank has.
      var streams = streamBank.vertexStreams;
      var lastTexCoordStream = null;
      var numTexCoordStreams = 0;
      for (var ii = 0; ii < streams.length; ++ii) {
        var stream = streams[ii];
        if (stream.semantic == o3djs.base.o3d.Stream.TEXCOORD) {
          lastTexCoordStream = stream;
          ++numTexCoordStreams;
        }
      }
      // Add any missing TEXCOORD streams. It might be more efficient for
      // the GPU to create an effect that doesn't need the extra streams
      // but this is a more generic solution because it means we can reuse
      // the same effect.
      for (var ii = numTexCoordStreams;
           ii < numTexCoordStreamsNeeded;
           ++ii) {
        streamBank.setVertexStream(
            lastTexCoordStream.semantic,
            lastTexCoordStream.semanticIndex + ii - numTexCoordStreams + 1,
            lastTexCoordStream.field,
            lastTexCoordStream.startIndex);
      }
    }
  }
};

/**
 * Copies an element and streambank or buffers so the two will share
 * streambanks, vertex and index buffers.
 * @param {!o3d.Pack} pack Pack to manage created objects.
 * @param {!o3d.Element} sourceElement The element to copy.
 * @return {!o3d.Element} the new copy of sourceElement.
 */
o3djs.element.duplicateElement = function(pack, sourceElement) {
  var newElement = pack.createObject(sourceElement.className);
  newElement.copyParams(sourceElement);
  // TODO: If we get the chance to parameterize the primitive's settings
  //     we can delete this code since copyParams will handle it.
  //     For now it only handles primitives by doing it manually.
  if (sourceElement.isAClassName('o3d.Primitive')) {
    newElement.indexBuffer = sourceElement.indexBuffer;
    newElement.startIndex = sourceElement.startIndex;
    newElement.primitiveType = sourceElement.primitiveType;
    newElement.numberVertices = sourceElement.numberVertices;
    newElement.numberPrimitives = sourceElement.numberPrimitives;
  }
  return newElement;
};

/**
 * Gets the normal for specific triangle in a Primitive in that Primitive's
 * local space.
 *
 * NOTE: THIS FUNCTION IS SLOW! If you want to do collisions you should use a
 * different solution.
 *
 * @param {!o3d.Primitive} primitive Primitive to get normal from. The
 *     primitive MUST be a TRIANGLELIST or a TRIANGLESTRIP and it must have a
 *     POSITION,0 stream.
 * @param {number} index Index of triangle.
 * @param {boolean} opt_winding The winding of the triangles of the
 *     Primitive. False = Clockwise, True = Counterclockwise. The default is
 *     false. This is only used for Primitives that have no normals.
 * @return {!o3djs.math.Vector3} The normal for the triangle.
 */
o3djs.element.getNormalForTriangle = function(primitive, index, opt_winding) {
  // Check that we can do this
  var primitiveType = primitive.primitiveType;
  if (primitiveType != o3djs.base.o3d.Primitive.TRIANGLELIST &&
      primitiveType != o3djs.base.o3d.Primitive.TRIANGLESTRIP) {
    throw 'primitive is not a TRIANGLELIST or TRIANGLESTRIP';
  }

  var indexBuffer = primitive.indexBuffer;
  var vertexIndex = (primitiveType == o3djs.base.o3d.Primitive.TRIANGLELIST) ?
                    (index * 3) : (index + 2);
  var vertexIndices;
  if (indexBuffer) {
    var indexField = indexBuffer.fields[0];
    vertexIndices = indexField.getAt(vertexIndex, 3);
  } else {
    vertexIndices = [vertexIndex, vertexIndex + 1, vertexIndex + 2]
  }

  var normalStream = primitive.streamBank.getVertexStream(
      o3djs.base.o3d.Stream.NORMAL, 0);
  if (normalStream) {
    var normalField = normalStream.field;
    // Look up the 3 normals that make the triangle.
    var summedNormal = [0, 0, 0];
    for (var ii = 0; ii < 3; ++ii) {
      var normal = normalField.getAt(vertexIndices[ii], 1);
      summedNormal = o3djs.math.addVector(summedNormal, normal);
    }
    return o3djs.math.normalize(summedNormal);
  } else {
    var positionStream = primitive.streamBank.getVertexStream(
        o3djs.base.o3d.Stream.POSITION, 0);
    if (!positionStream) {
      throw 'no POSITION,0 stream in primitive';
    }
    var positionField = positionStream.field;
    // Lookup the 3 positions that make the triangle.
    var positions = [];
    for (var ii = 0; ii < 3; ++ii) {
      positions[ii] = positionField.getAt(vertexIndices[ii], 1);
    }

    // Compute a face normal from the positions.
    var v0 = o3djs.math.normalize(o3djs.math.subVector(positions[1],
                                                       positions[0]));
    var v1 = o3djs.math.normalize(o3djs.math.subVector(positions[2],
                                                       positions[1]));
    return opt_winding ? o3djs.math.cross(v1, v0) : o3djs.math.cross(v0, v1);
  }
};


/*
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview This file contains various event related functions for
 * o3d.  It puts them in the 'event' module on the o3djs object.
 *
 * TODO Add selenium tests.
 *
 *
 */
o3djs.provide('o3djs.event');

/**
 * A Module for handling events related to o3d and various browsers.
 * @namespace
 */
o3djs.event = o3djs.event || {};

/**
  * @param {string} inStr base string.
  * @param {string} extraStr string to append.
  * @return {string} inStr + ' ' + extraStr, or just inStr if extraStr is ''.
  */
o3djs.event.appendWithSpace = function(inStr, extraStr) {
  return (inStr.length == 0) ? extraStr : inStr + ' ' + extraStr;
};

/**
  * @param {boolean} state whether to append or not.
  * @param {string} inStr base string.
  * @param {string} extraStr string to append.
  * @return {string} inStr + ' ' + extraStr, or just inStr if state is false.
  */
o3djs.event.appendWithSpaceIf = function(state, inStr, extraStr) {
  return (state) ? o3djs.event.appendWithSpace(inStr, extraStr) : inStr;
};


/**
 * Builds a DOM-level 3 modifier string for a KeyboardEvent - see
 * http://www.w3.org/TR/DOM-Level-3-Events/events.html
 * #Events-KeyboardEvents-Interfaces.
 * @param {boolean} control whether the control key is down.
 * @param {boolean} alt whether the alt/option key is down.
 * @param {boolean} shift whether the shift key is down.
 * @param {boolean} meta whether the meta/command key is down.
 * @return {string} space delimited list of keys that are down.
 */
o3djs.event.getModifierString = function(control, alt, shift, meta) {
  var modStr = o3djs.event.appendWithSpaceIf(control, '', 'Control');
  modStr = o3djs.event.appendWithSpaceIf(alt, modStr, 'Alt');
  modStr = o3djs.event.appendWithSpaceIf(shift, modStr, 'Shift');
  return o3djs.event.appendWithSpaceIf(meta, modStr, 'Meta');
};


/**
 * Pad a string with leading zeroes if needed until it is the length desired.
 * @param {string} str The input string, probably representing a number.
 * @param {number} to_length The desired minimum length of string with padding.
 * @return {string} A string padded with leading zeroes as needed to be the
 * length desired.
 */
o3djs.event.padWithLeadingZeroes = function(str, to_length) {
  while (str.length < to_length)
    str = '0' + str;
  return str;
};

/**
 * Creates a keyIdentifer string for a given keystroke as specified in the w3c
 * spec on http://www.w3.org/TR/DOM-Level-3-Events/events.html.
 * @param {number} charCode numeric unicode code point as reported by the OS.
 * @param {number} keyCode numeric keyCode as reported by the OS, currently
 * unused but will probably be necessary in the future.
 * @return {string} eg 'Left' or 'U+0040'.
 */
o3djs.event.getKeyIdentifier = function(charCode, keyCode) {
  if (!charCode) {
    // TODO: This works for webkit for keydown and keyup, for basic
    // alphanumeric keys, at least.  Likely it needs lots of work to handle
    // accented characters, various keyboards, etc., as does the rest of our
    // keyboard event code.
    charCode = keyCode;
  }
  switch (charCode) {
    case 3: case 13: return 'Enter';  // spec merges these.
    case 37: return 'Left';
    case 39: return 'Right';
    case 38: return 'Up';
    case 40: return 'Down';
  }
  charCode = (charCode >= 97 && charCode <= 122) ? charCode - 32 : charCode;
  var keyStr = charCode.toString(16).toUpperCase();
  return 'U+' + o3djs.event.padWithLeadingZeroes(keyStr, 4);
};


/** Takes a keyIdentifier string and remaps it to an ASCII/Unicode value
 *  suitable for javascript event handling.
 * @param {string} keyIdent a keyIdentifier string as generated above.
 * @return {number} the numeric Unicode code point represented.
 */
o3djs.event.keyIdentifierToChar = function(keyIdent) {
  if (keyIdent && typeof(keyIdent) == 'string') {
    switch (keyIdent) {
      case 'Enter': return 13;
      case 'Left': return 37;
      case 'Right': return 39;
      case 'Up': return 38;
      case 'Down': return 40;
    }
    if (keyIdent.indexOf('U+') == 0)
      return parseInt(keyIdent.substr(2).toUpperCase(), 16);
  }
  return 0;
};

/**
 *  Extracts the key char in number form from the event, in a cross-browser
 *  manner.
 * @param {!Event} event .
 * @return {number} unicode code point for the key.
 */
o3djs.event.getEventKeyChar = function(event) {
  if (!event) {
    event = window.event;
  }
  var charCode = 0;
  if (event.keyIdentifier)
    charCode = o3djs.event.keyIdentifierToChar(event.keyIdentifier);
  if (!charCode)
    charCode = (window.event) ? window.event.keyCode : event.charCode;
  if (!charCode)
    charCode = event.keyCode;
  return charCode;
};


/**
 * Cancel an event we've handled so it stops propagating upwards.
 * The cancelBubble is for IE, stopPropagation is for all other browsers.
 * preventDefault ensures that the default action is also canceled.
 * @param {!Event} event - the event to cancel.
 */
o3djs.event.cancel = function(event) {
  if (!event)
    event = window.event;
  event.cancelBubble = true;
  if (event.stopPropagation)
    event.stopPropagation();
  if (event.preventDefault)
    event.preventDefault();
};

/**
 * Convenience function to setup synthesizing and dispatching of keyboard events
 * whenever the focussed plug-in calls Javascript to report a keyboard action.
 * @param {!Element} pluginObject the <object> where the o3d plugin lives,
 * which the caller probably obtained by calling getElementById.
 */
o3djs.event.startKeyboardEventSynthesis = function(pluginObject) {
  var handler = function(event) {
    o3djs.event.onKey(event, pluginObject);
  };

  o3djs.event.addEventListener(pluginObject, 'keypress', handler);
  o3djs.event.addEventListener(pluginObject, 'keydown', handler);
  o3djs.event.addEventListener(pluginObject, 'keyup', handler);
};

/**
 * Dispatches a DOM-level 3 KeyboardEvent when called back by the plugin.
 * see http://www.w3.org/TR/DOM-Level-3-Events/events.html
 * #Events-KeyboardEvents-Interfaces
 * see http://developer.mozilla.org/en/DOM/event.initKeyEvent
 * @param {!Event} event an O3D event object.
 * @param {!Element} pluginObject the plugin object on the page.
 */
o3djs.event.onKey = function(event, pluginObject) {
  var k_evt = o3djs.event.createKeyEvent(event.type, event.charCode,
      event.keyCode, event.ctrlKey, event.altKey, event.shiftKey,
      event.metaKey);
  if (k_evt) {
    if (pluginObject.parentNode.dispatchEvent) {
      // Using the pluginObject itself fails for non-capturing event listeners
      // on keypress events on Firefox only, as far as I've been able to
      // determine.  I have no idea why.
      pluginObject.parentNode.dispatchEvent(k_evt);
    } else if (pluginObject.fireEvent) {
      pluginObject.fireEvent('on' + event.type, k_evt);
    }
  }
};

/**
 * Creates a DOM-level 3 KeyboardEvent.
 * see http://www.w3.org/TR/DOM-Level-3-Events/events.html
 * #Events-KeyboardEvents-Interfaces.
 * see http://developer.mozilla.org/en/DOM/event.initKeyEvent
 * @param {string} eventName one of 'keypress', 'keydown' or 'keyup'.
 * @param {number} charCode the character code for the key.
 * @param {number} keyCode the key code for the key.
 * @param {boolean} control whether the control key is down.
 * @param {boolean} alt whether the alt/option key is down.
 * @param {boolean} shift whether the shift key is down.
 * @param {boolean} meta whether the meta/command key is down.
 */
o3djs.event.createKeyEvent = function(eventName, charCode, keyCode,
                                      control, alt, shift, meta) {
  var k_evt;
  var keyIdentifier = o3djs.event.getKeyIdentifier(charCode, keyCode);
  if (document.createEvent) {
    k_evt = document.createEvent('KeyboardEvent');
    if (k_evt.initKeyboardEvent) {  // WebKit.
      k_evt.initKeyboardEvent(eventName, true, true, window,
                   keyIdentifier, 0,
                   control, alt, shift, meta);
      // TODO: These actually fail to do anything in Chrome; those are
      // read-only fields, and it's not setting them in initKeyboardEvent.
      k_evt.charCode = charCode;
      if (eventName == 'keypress')
        k_evt.keyCode = charCode;
      else
        k_evt.keyCode = keyCode;
    } else if (k_evt.initKeyEvent) {  // FF.
      k_evt.initKeyEvent(eventName, true, true, window,
                         control, alt, shift, meta, keyCode, charCode);
      k_evt.keyIdentifier = keyIdentifier;
    }
  } else if (document.createEventObject) {
    k_evt = document.createEventObject();
    k_evt.ctrlKey = control;
    k_evt.altKey = alt;
    k_evt.shiftKey = shift;
    k_evt.metaKey = meta;
    k_evt.keyCode = charCode;  // Emulate IE charcode-in-the-keycode onkeypress.
    k_evt.keyIdentifier = keyIdentifier;
  }
  k_evt.synthetic = true;
  return k_evt;
};

/*
 * Function to create a closure that will call each event handler in an array
 * whenever it gets called, passing its single argument through to the
 * sub-handlers.  The sub-handlers may either be functions or EventListeners.
 * This is generally expected to be used only through
 * o3djs.event.addEventListener.
 * @param {!Array.<*>} listenerSet an array of handlers.
 * @return {!function(*): void} a closure to be used to multiplex out
 *     event-handling.
 */
o3djs.event.createEventHandler = function(listenerSet) {
  return function(event) {
    var length = listenerSet.length;
    for (var index = 0; index < length; ++index) {
      var handler = listenerSet[index];
      if (typeof(handler.handleEvent) == 'function') {
        handler.handleEvent(event);
      } else {
        handler(event);
      }
    }
  }
};

/**
 * Convenience function to manage event listeners on the o3d plugin object,
 * intended as a drop-in replacement for the DOM addEventListener [with slightly
 * different arguments, but the same effect].
 * @param {!Element} pluginObject the html object where the o3d plugin lives,
 * which the caller probably obtained by calling getElementById or makeClients.
 * @param {string} type the event type on which to trigger, e.g. 'mousedown',
 * 'mousemove', etc.
 * @param {!Object} handler either a function or an EventListener object.
 */
o3djs.event.addEventListener = function(pluginObject, type, handler) {
  if (!handler || typeof(type) != 'string' ||
      (typeof(handler) != 'function' &&
       typeof(handler.handleEvent) != 'function')) {
    throw new Error('Invalid argument.');
  }
  pluginObject.o3d_eventRegistry = pluginObject.o3d_eventRegistry || [];
  var registry = pluginObject.o3d_eventRegistry;
  var listenerSet = registry[type];
  if (!listenerSet || listenerSet.length == 0) {
    listenerSet = registry[type] = [];
    pluginObject.client.setEventCallback(type,
        o3djs.event.createEventHandler(listenerSet));
  } else {
    for (var index in listenerSet) {
      if (listenerSet[index] == handler) {
        return;  // We're idempotent.
      }
    }
  }
  listenerSet.push(handler);
};


/**
 * Convenience function to manage event listeners on the o3d plugin object,
 * intended as a drop-in replacement for the DOM removeEventListener [with
 * slightly different arguments, but the same effect].
 * @param {!Element} pluginObject the <object> where the o3d plugin lives,
 * which the caller probably obtained by calling getElementById.
 * @param {string} type the event type on which the handler to be removed was to
 * trigger, e.g. 'mousedown', 'mousemove', etc.
 * @param {!Object} handler either a function or an EventListener object.
 */
o3djs.event.removeEventListener = function(pluginObject, type, handler) {
  var registry = pluginObject.o3d_eventRegistry;
  if (!registry) {
    return;
  }
  var listenerSet = registry[type];
  if (!listenerSet) {
    return;
  }
  for (var index in listenerSet) {
    if (listenerSet[index] == handler) {
      if (listenerSet.length == 1) {
        pluginObject.client.clearEventCallback(type);
      }
      listenerSet.splice(index, 1);
      break;
    }
  }
};
/*
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview This file contains a loader class for helping to load
 *     muliple assets in an asynchronous manner.
 */

o3djs.provide('o3djs.loader');

/**
 * A Module with a loader class for helping to load muliple assets in an
 * asynchronous manner.
 * @namespace
 */
o3djs.loader = o3djs.loader || {};

/**
 * A simple Loader class to call some callback when everything has loaded.
 * @constructor
 * @param {!function(): void} onFinished Function to call when final item has
 *        loaded.
 */
o3djs.loader.Loader = function(onFinished)  {
  this.count_ = 1;
  this.onFinished_ = onFinished;

  /**
   * The LoadInfo for this loader you can use to track progress.
   * @type {!o3djs.io.LoadInfo}
   */
  this.loadInfo = o3djs.io.createLoadInfo();
};

/**
 * Creates a Loader for helping to load a bunch of items asychronously.
 *
 * The way you use this is as follows.
 *
 * <pre>
 * var loader = o3djs.loader.createLoader(myFinishedCallback);
 * loader.loadTexture(pack, texture1Url, callbackForTexture);
 * loader.loadTexture(pack, texture2Url, callbackForTexture);
 * loader.loadTexture(pack, texture3Url, callbackForTexture);
 * loader.finish();
 * </pre>
 *
 * The loader guarantees that myFinishedCallback will be called after
 * all the items have been loaded.
 *
* @param {!function(): void} onFinished Function to call when final item has
*        loaded.
* @return {!o3djs.loader.Loader} A Loader Object.
 */
o3djs.loader.createLoader = function(onFinished) {
  return new o3djs.loader.Loader(onFinished);
};

/**
 * Loads a texture.
 * @param {!o3d.Pack} pack Pack to load texture into.
 * @param {string} url URL of texture to load.
 * @param {!function(o3d.Texture, *): void} opt_onTextureLoaded
 *     optional callback when texture is loaded. It will be passed the texture
 *     and an exception which is null on success.
 */
o3djs.loader.Loader.prototype.loadTexture = function(pack,
                                                     url,
                                                     opt_onTextureLoaded) {
  var that = this;  // so the function below can see "this".
  ++this.count_;
  var loadInfo = o3djs.io.loadTexture(pack, url, function(texture, exception) {
    if (opt_onTextureLoaded) {
      opt_onTextureLoaded(texture, exception);
    }
    that.countDown_();
  });
  this.loadInfo.addChild(loadInfo);
};

/**
 * Loads a RawData.
 * @param {!o3d.Pack} pack Pack to load texture into.
 * @param {string} url URL of image file to load.
 * @param {!function(!o3d.FileRequest, o3d.RawData, *): void} onLoaded Callback
 *     when RawData is loaded. It will be passed the request, a RawData and an
 *     exception which is null on success. The RawData is associated with
 *     the request so it will stay in memory until you free with request with
 *     pack.removeObject(request).
 */
o3djs.loader.Loader.prototype.loadRawData = function(pack,
                                                     url,
                                                     onLoaded) {
  var that = this;  // so the function below can see "this".
  ++this.count_;
  var loadInfo = o3djs.io.loadRawData(
      pack, url, function(request, rawData, exception) {
    onLoaded(request, rawData, exception);
    that.countDown_();
  });
  this.loadInfo.addChild(loadInfo);
};

/**
 * Loads bitmaps.
 * @param {!o3d.Pack} pack Pack to load texture into.
 * @param {string} url URL of image file to load.
 * @param {!function(!Array.<!o3d.Bitmap>, *): void} onBitmapsLoaded Callback
 *     when bitmaps are loaded. It will be passed an array of bitmaps and an
 *     exception which is null on success.
 */
o3djs.loader.Loader.prototype.loadBitmaps = function(pack,
                                                     url,
                                                     onBitmapsLoaded) {
  var that = this;  // so the function below can see "this".
  ++this.count_;
  var loadInfo = o3djs.io.loadBitmaps(pack, url, function(bitmaps, exception) {
    onBitmapsLoaded(bitmaps, exception);
    that.countDown_();
  });
  this.loadInfo.addChild(loadInfo);
};

/**
 * Loads a 3d scene.
 * @param {!o3d.Client} client An O3D client object.
 * @param {!o3d.Pack} pack Pack to load texture into.
 * @param {!o3d.Transform} parent Transform to parent scene under.
 * @param {string} url URL of scene to load.
 * @param {!o3djs.serialization.Options} opt_options Options passed into the
 *     loader.
 * @param {!function(!o3d.Pack, !o3d.Transform, *): void}
 *     opt_onSceneLoaded optional callback when scene is loaded. It will be
 *     passed the pack and parent and an exception which is null on success.
 */
o3djs.loader.Loader.prototype.loadScene = function(client,
                                                   pack,
                                                   parent,
                                                   url,
                                                   opt_onSceneLoaded,
                                                   opt_options) {
  var that = this;  // so the function below can see "this".
  ++this.count_;
  var loadInfo = o3djs.scene.loadScene(
      client, pack, parent, url, function(pack, parent, exception) {
        if (opt_onSceneLoaded) {
          opt_onSceneLoaded(pack, parent, exception);
        }
        that.countDown_();
      },
      opt_options);
  this.loadInfo.addChild(loadInfo);
};

/**
 * Loads a text file.
 * @param {string} url URL of scene to load.
 * @param {!function(string, *): void} onTextLoaded Function to call when
 *     the file is loaded. It will be passed the contents of the file as a
 *     string and an exception which is null on success.
 */
o3djs.loader.Loader.prototype.loadTextFile = function(url, onTextLoaded) {
  var that = this;  // so the function below can see "this".
  ++this.count_;
  var loadInfo = o3djs.io.loadTextFile(url, function(string, exception) {
    onTextLoaded(string, exception);
    that.countDown_();
  });
  this.loadInfo.addChild(loadInfo);
};

/**
 * Creates a loader that is tracked by this loader so that when the new loader
 * is finished it will be reported to this loader.
 * @param {!function(): void} onFinished Function to be called when everything
 *      loaded with this loader has finished.
 * @return {!o3djs.loader.Loader} The new Loader.
 */
o3djs.loader.Loader.prototype.createLoader = function(onFinished) {
  var that = this;
  ++this.count_;
  var loader = o3djs.loader.createLoader(function() {
      onFinished();
      that.countDown_();
  });
  this.loadInfo.addChild(loader.loadInfo);
  return loader;
};

/**
 * Counts down the internal count and if it gets to zero calls the callback.
 * @private
 */
o3djs.loader.Loader.prototype.countDown_ = function() {
  --this.count_;
  if (this.count_ === 0) {
    this.onFinished_();
  }
};

/**
 * Finishes the loading process.
 * Actually this just calls countDown_ to account for the count starting at 1.
 */
o3djs.loader.Loader.prototype.finish = function() {
  this.countDown_();
};


/*
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview This file contains matrix/vector math functions.
 * It adds them to the "math" module on the o3djs object.
 *
 * o3djs.math supports a row-major and a column-major mode.  In both
 * modes, vectors are stored as arrays of numbers, and matrices are stored as
 * arrays of arrays of numbers.
 *
 * In row-major mode:
 *
 * - Rows of a matrix are sub-arrays.
 * - Individual entries of a matrix M get accessed in M[row][column] fashion.
 * - Tuples of coordinates are interpreted as row-vectors.
 * - A vector v gets transformed by a matrix M by multiplying in the order v*M.
 *
 * In column-major mode:
 *
 * - Columns of a matrix are sub-arrays.
 * - Individual entries of a matrix M get accessed in M[column][row] fashion.
 * - Tuples of coordinates are interpreted as column-vectors.
 * - A matrix M transforms a vector v by multiplying in the order M*v.
 *
 * When a function in o3djs.math requires separate row-major and
 * column-major versions, a function with the same name gets added to each of
 * the namespaces o3djs.math.rowMajor and o3djs.math.columnMajor. The
 * function installRowMajorFunctions() or the function
 * installColumnMajorFunctions() should get called during initialization to
 * establish the mode.  installRowMajorFunctions() works by iterating through
 * the o3djs.math.rowMajor namespace and for each function foo, setting
 * o3djs.math.foo equal to o3djs.math.rowMajor.foo.
 * installRowMajorFunctions() works the same way, iterating over the columnMajor
 * namespace.  At the end of this file, we call installRowMajorFunctions().
 *
 * Switching modes changes two things.  It changes how a matrix is encoded as an
 * array, and it changes how the entries of a matrix get interpreted.  Because
 * those two things change together, the matrix representing a given
 * transformation of space is the same JavaScript object in either mode.
 * One consequence of this is that very few functions require separate row-major
 * and column-major versions.  Typically, a function requires separate versions
 * only if it makes matrix multiplication order explicit, like
 * mulMatrixMatrix(), mulMatrixVector(), or mulVectorMatrix().  Functions which
 * create a new matrix, like scaling(), rotationZYX(), and translation() return
 * the same JavaScript object in either mode, and functions which implicitly
 * multiply like scale(), rotateZYX() and translate() modify the matrix in the
 * same way in either mode.
 *
 * The convention choice made for math functions in this library is independent
 * of the convention choice for how matrices get loaded into shaders.  That
 * convention is determined on a per-shader basis.
 *
 * Other utilities in o3djs should avoid making calls to functions that make
 * multiplication order explicit.  Instead they should appeal to functions like:
 *
 * o3djs.math.matrix4.transformPoint
 * o3djs.math.matrix4.transformDirection
 * o3djs.math.matrix4.transformNormal
 * o3djs.math.matrix4.transformVector4
 * o3djs.math.matrix4.composition
 * o3djs.math.matrix4.compose
 *
 * These functions multiply matrices implicitly and internally choose the
 * multiplication order to get the right result.  That way, utilities which use
 * o3djs.math work in either major mode.  Note that this does not necessarily
 * mean all sample code will work even if a line is added which switches major
 * modes, but it does mean that calls to o3djs still do what they are supposed
 * to.
 *
 */

o3djs.provide('o3djs.math');

/**
 * A module for math for o3djs.math.
 * @namespace
 */
o3djs.math = o3djs.math || {};

/**
 * A random seed for the pseudoRandom function.
 * @private
 * @type {number}
 */
o3djs.math.randomSeed_ = 0;

/**
 * A constant for the pseudoRandom function
 * @private
 * @type {number}
 */
o3djs.math.RANDOM_RANGE_ = Math.pow(2, 32);

/**
 * Functions which deal with 4-by-4 transformation matrices are kept in their
 * own namespsace.
 * @namespace
 */
o3djs.math.matrix4 = o3djs.math.matrix4 || {};

/**
 * Functions that are specifically row major are kept in their own namespace.
 * @namespace
 */
o3djs.math.rowMajor = o3djs.math.rowMajor || {};

/**
 * Functions that are specifically column major are kept in their own namespace.
 * @namespace
 */
o3djs.math.columnMajor = o3djs.math.columnMajor || {};

/**
 * Functions that do error checking are stored in their own namespace.
 * @namespace
 */
o3djs.math.errorCheck = o3djs.math.errorCheck || {};

/**
 * Functions that do no error checking and have a separate version that does in
 * o3djs.math.errorCheck are stored in their own namespace.
 * @namespace
 */
o3djs.math.errorCheckFree = o3djs.math.errorCheckFree || {};

/**
 * An Array of 2 floats
 * @type {(!Array.<number>|!o3d.Float2)}
 */
o3djs.math.Vector2 = goog.typedef;

/**
 * An Array of 3 floats
 * @type {(!Array.<number>|!o3d.Float3)}
 */
o3djs.math.Vector3 = goog.typedef;

/**
 * An Array of 4 floats
 * @type {(!Array.<number>|!o3d.Float4)}
 */
o3djs.math.Vector4 = goog.typedef;

/**
 * An Array of floats.
 * @type {!Array.<number>}
 */
o3djs.math.Vector = goog.typedef;

/**
 * A 1x1 Matrix of floats
 * @type {!Array.<!Array.<number>>}
 */
o3djs.math.Matrix1 = goog.typedef;

/**
 * A 2x2 Matrix of floats
 * @type {!Array.<!Array.<number>>}
 */
o3djs.math.Matrix2 = goog.typedef;

/**
 * A 3x3 Matrix of floats
 * @type {!Array.<!Array.<number>>}
 */
o3djs.math.Matrix3 = goog.typedef;

/**
 * A 4x4 Matrix of floats
 * @type {(!Array.<!Array.<number>>|!o3d.Matrix4)}
 */
o3djs.math.Matrix4 = goog.typedef;

/**
 * A arbitrary size Matrix of floats
 * @type {(!Array.<!Array.<number>>|!o3d.Matrix4)}
 */
o3djs.math.Matrix = goog.typedef;

/**
 * Returns a deterministic pseudorandom number between 0 and 1
 * @return {number} a random number between 0 and 1
 */
o3djs.math.pseudoRandom = function() {
  var math = o3djs.math;
  return (math.randomSeed_ =
          (134775813 * math.randomSeed_ + 1) %
          math.RANDOM_RANGE_) / math.RANDOM_RANGE_;
};

/**
 * Resets the pseudoRandom function sequence.
 */
o3djs.math.resetPseudoRandom = function() {
  o3djs.math.randomSeed_ = 0;
};

/**
 * Converts degrees to radians.
 * @param {number} degrees A value in degrees.
 * @return {number} the value in radians.
 */
o3djs.math.degToRad = function(degrees) {
  return degrees * Math.PI / 180;
};

/**
 * Converts radians to degrees.
 * @param {number} radians A value in radians.
 * @return {number} the value in degrees.
 */
o3djs.math.radToDeg = function(radians) {
  return radians * 180 / Math.PI;
};

/**
 * Performs linear interpolation on two scalars.
 * Given scalars a and b and interpolation coefficient t, returns
 * (1 - t) * a + t * b.
 * @param {number} a Operand scalar.
 * @param {number} b Operand scalar.
 * @param {number} t Interpolation coefficient.
 * @return {number} The weighted sum of a and b.
 */
o3djs.math.lerpScalar = function(a, b, t) {
  return (1 - t) * a + t * b;
};

/**
 * Adds two vectors; assumes a and b have the same dimension.
 * @param {!o3djs.math.Vector} a Operand vector.
 * @param {!o3djs.math.Vector} b Operand vector.
 * @return {!o3djs.math.Vector} The sum of a and b.
 */
o3djs.math.addVector = function(a, b) {
  var r = [];
  var aLength = a.length;
  for (var i = 0; i < aLength; ++i)
    r[i] = a[i] + b[i];
  return r;
};

/**
 * Subtracts two vectors.
 * @param {!o3djs.math.Vector} a Operand vector.
 * @param {!o3djs.math.Vector} b Operand vector.
 * @return {!o3djs.math.Vector} The difference of a and b.
 */
o3djs.math.subVector = function(a, b) {
  var r = [];
  var aLength = a.length;
  for (var i = 0; i < aLength; ++i)
    r[i] = a[i] - b[i];
  return r;
};

/**
 * Performs linear interpolation on two vectors.
 * Given vectors a and b and interpolation coefficient t, returns
 * (1 - t) * a + t * b.
 * @param {!o3djs.math.Vector} a Operand vector.
 * @param {!o3djs.math.Vector} b Operand vector.
 * @param {number} t Interpolation coefficient.
 * @return {!o3djs.math.Vector} The weighted sum of a and b.
 */
o3djs.math.lerpVector = function(a, b, t) {
  var r = [];
  var aLength = a.length;
  for (var i = 0; i < aLength; ++i)
    r[i] = (1 - t) * a[i] + t * b[i];
  return r;
};

/**
 * Clamps a value between 0 and range using a modulo.
 * @param {number} v Value to clamp mod.
 * @param {number} range Range to clamp to.
 * @param {number} opt_rangeStart start of range. Default = 0.
 * @return {number} Clamp modded value.
 */
o3djs.math.modClamp = function(v, range, opt_rangeStart) {
  var start = opt_rangeStart || 0;
  if (range < 0.00001) {
    return start;
  }
  v -= start;
  if (v < 0) {
    v -= Math.floor(v / range) * range;
  } else {
    v = v % range;
  }
  return v + start;
};

/**
 * Lerps in a circle.
 * Does a lerp between a and b but inside range so for example if
 * range is 100, a is 95 and b is 5 lerping will go in the positive direction.
 * @param {number} a Start value.
 * @param {number} b Target value.
 * @param {number} t Amount to lerp (0 to 1).
 * @param {number} range Range of circle.
 * @return {number} lerped result.
 */
o3djs.math.lerpCircular = function(a, b, t, range) {
  a = o3djs.math.modClamp(a, range);
  b = o3djs.math.modClamp(b, range);
  var delta = b - a;
  if (Math.abs(delta) > range * 0.5) {
    if (delta > 0) {
      b -= range;
    } else {
      b += range;
    }
  }
  return o3djs.math.modClamp(o3djs.math.lerpScalar(a, b, t), range);
};

/**
 * Lerps radians.
 * @param {number} a Start value.
 * @param {number} b Target value.
 * @param {number} t Amount to lerp (0 to 1).
 * @return {number} lerped result.
 */
o3djs.math.lerpRadian = function(a, b, t) {
  return o3djs.math.lerpCircular(a, b, t, Math.PI * 2);
};

/**
 * Divides a vector by a scalar.
 * @param {!o3djs.math.Vector} v The vector.
 * @param {number} k The scalar.
 * @return {!o3djs.math.Vector} v The vector v divided by k.
 */
o3djs.math.divVectorScalar = function(v, k) {
  var r = [];
  var vLength = v.length;
  for (var i = 0; i < vLength; ++i)
    r[i] = v[i] / k;
  return r;
};

/**
 * Computes the dot product of two vectors; assumes that a and b have
 * the same dimension.
 * @param {!o3djs.math.Vector} a Operand vector.
 * @param {!o3djs.math.Vector} b Operand vector.
 * @return {number} The dot product of a and b.
 */
o3djs.math.dot = function(a, b) {
  var r = 0.0;
  var aLength = a.length;
  for (var i = 0; i < aLength; ++i)
    r += a[i] * b[i];
  return r;
};

/**
 * Computes the cross product of two vectors; assumes both vectors have
 * three entries.
 * @param {!o3djs.math.Vector} a Operand vector.
 * @param {!o3djs.math.Vector} b Operand vector.
 * @return {!o3djs.math.Vector} The vector a cross b.
 */
o3djs.math.cross = function(a, b) {
  return [a[1] * b[2] - a[2] * b[1],
          a[2] * b[0] - a[0] * b[2],
          a[0] * b[1] - a[1] * b[0]];
};

/**
 * Computes the Euclidean length of a vector, i.e. the square root of the
 * sum of the squares of the entries.
 * @param {!o3djs.math.Vector} a The vector.
 * @return {number} The length of a.
 */
o3djs.math.length = function(a) {
  var r = 0.0;
  var aLength = a.length;
  for (var i = 0; i < aLength; ++i)
    r += a[i] * a[i];
  return Math.sqrt(r);
};

/**
 * Computes the square of the Euclidean length of a vector, i.e. the sum
 * of the squares of the entries.
 * @param {!o3djs.math.Vector} a The vector.
 * @return {number} The square of the length of a.
 */
o3djs.math.lengthSquared = function(a) {
  var r = 0.0;
  var aLength = a.length;
  for (var i = 0; i < aLength; ++i)
    r += a[i] * a[i];
  return r;
};

/**
 * Computes the Euclidean distance between two vectors.
 * @param {!o3djs.math.Vector} a A vector.
 * @param {!o3djs.math.Vector} b A vector.
 * @return {number} The distance between a and b.
 */
o3djs.math.distance = function(a, b) {
  var r = 0.0;
  var aLength = a.length;
  for (var i = 0; i < aLength; ++i) {
    var t = a[i] - b[i];
    r += t * t;
  }
  return Math.sqrt(r);
};

/**
 * Computes the square of the Euclidean distance between two vectors.
 * @param {!o3djs.math.Vector} a A vector.
 * @param {!o3djs.math.Vector} b A vector.
 * @return {number} The distance between a and b.
 */
o3djs.math.distanceSquared = function(a, b) {
  var r = 0.0;
  var aLength = a.length;
  for (var i = 0; i < aLength; ++i) {
    var t = a[i] - b[i];
    r += t * t;
  }
  return r;
};

/**
 * Divides a vector by its Euclidean length and returns the quotient.
 * @param {!o3djs.math.Vector} a The vector.
 * @return {!o3djs.math.Vector} The normalized vector.
 */
o3djs.math.normalize = function(a) {
  var r = [];
  var n = 0.0;
  var aLength = a.length;
  for (var i = 0; i < aLength; ++i)
    n += a[i] * a[i];
  n = Math.sqrt(n);
  for (var i = 0; i < aLength; ++i)
    r[i] = a[i] / n;
  return r;
};

/**
 * Adds two matrices; assumes a and b are the same size.
 * @param {!o3djs.math.Matrix} a Operand matrix.
 * @param {!o3djs.math.Matrix} b Operand matrix.
 * @return {!o3djs.math.Matrix} The sum of a and b.
 */
o3djs.math.addMatrix = function(a, b) {
  var r = [];
  var aLength = a.length;
  var a0Length = a[0].length;
  for (var i = 0; i < aLength; ++i) {
    var row = [];
    var ai = a[i];
    var bi = b[i];
    for (var j = 0; j < a0Length; ++j)
      row[j] = ai[j] + bi[j];
    r[i] = row;
  }
  return r;
};

/**
 * Subtracts two matrices; assumes a and b are the same size.
 * @param {!o3djs.math.Matrix} a Operand matrix.
 * @param {!o3djs.math.Matrix} b Operand matrix.
 * @return {!o3djs.math.Matrix} The sum of a and b.
 */
o3djs.math.subMatrix = function(a, b) {
  var r = [];
  var aLength = a.length;
  var a0Length = a[0].length;
  for (var i = 0; i < aLength; ++i) {
    var row = [];
    var ai = a[i];
    var bi = b[i];
    for (var j = 0; j < a0Length; ++j)
      row[j] = ai[j] - bi[j];
    r[i] = row;
  }
  return r;
};

/**
 * Performs linear interpolation on two matrices.
 * Given matrices a and b and interpolation coefficient t, returns
 * (1 - t) * a + t * b.
 * @param {!o3djs.math.Matrix} a Operand matrix.
 * @param {!o3djs.math.Matrix} b Operand matrix.
 * @param {number} t Interpolation coefficient.
 * @return {!o3djs.math.Matrix} The weighted of a and b.
 */
o3djs.math.lerpMatrix = function(a, b, t) {
  var r = [];
  var aLength = a.length;
  var a0Length = a[0].length;
  for (var i = 0; i < aLength; ++i) {
    var row = [];
    var ai = a[i];
    var bi = b[i];
    for (var j = 0; j < a0Length; ++j)
      row[j] = (1 - t) * ai[j] + t * bi[j];
    r[i] = row;
  }
  return r;
};

/**
 * Divides a matrix by a scalar.
 * @param {!o3djs.math.Matrix} m The matrix.
 * @param {number} k The scalar.
 * @return {!o3djs.math.Matrix} The matrix m divided by k.
 */
o3djs.math.divMatrixScalar = function(m, k) {
  var r = [];
  var mLength = m.length;
  var m0Length = m[0].length;
  for (var i = 0; i < mLength; ++i) {
    r[i] = [];
    for (var j = 0; j < m0Length; ++j)
      r[i][j] = m[i][j] / k;
  }
  return r;
};

/**
 * Negates a scalar.
 * @param {number} a The scalar.
 * @return {number} -a.
 */
o3djs.math.negativeScalar = function(a) {
 return -a;
};

/**
 * Negates a vector.
 * @param {!o3djs.math.Vector} v The vector.
 * @return {!o3djs.math.Vector} -v.
 */
o3djs.math.negativeVector = function(v) {
 var r = [];
 var vLength = v.length;
 for (var i = 0; i < vLength; ++i) {
   r[i] = -v[i];
 }
 return r;
};

/**
 * Negates a matrix.
 * @param {!o3djs.math.Matrix} m The matrix.
 * @return {!o3djs.math.Matrix} -m.
 */
o3djs.math.negativeMatrix = function(m) {
 var r = [];
 var mLength = m.length;
 var m0Length = m[0].length;
 for (var i = 0; i < mLength; ++i) {
   r[i] = [];
   for (var j = 0; j < m0Length; ++j)
     r[i][j] = -m[i][j];
 }
 return r;
};

/**
 * Copies a scalar.
 * @param {number} a The scalar.
 * @return {number} a.
 */
o3djs.math.copyScalar = function(a) {
  return a;
};

/**
 * Copies a vector.
 * @param {!o3djs.math.Vector} v The vector.
 * @return {!o3djs.math.Vector} A copy of v.
 */
o3djs.math.copyVector = function(v) {
  var r = [];
  for (var i = 0; i < v.length; i++)
    r[i] = v[i];
  return r;
};

/**
 * Copies a matrix.
 * @param {!o3djs.math.Matrix} m The matrix.
 * @return {!o3djs.math.Matrix} A copy of m.
 */
o3djs.math.copyMatrix = function(m) {
  var r = [];
  var mLength = m.length;
  for (var i = 0; i < mLength; ++i) {
    r[i] = [];
    for (var j = 0; j < m[i].length; j++) {
      r[i][j] = m[i][j];
    }
  }
  return r;
};

/**
 * Returns the elements of a matrix as a one-dimensional array. The
 * rows or columns (depending on whether the matrix is row-major or
 * column-major) are concatenated.
 * @param {!o3djs.math.Matrix} m The matrix.
 * @return {!Array.<number>} The matrix's elements as a one-dimensional array.
 */
o3djs.math.getMatrixElements = function(m) {
  var r = [];
  var mLength = m.length;
  var k = 0;
  for (var i = 0; i < mLength; i++) {
    for (var j = 0; j < m[i].length; j++) {
      r[k++] = m[i][j];
    }
  }
  return r;
};

/**
 * Multiplies two scalars.
 * @param {number} a Operand scalar.
 * @param {number} b Operand scalar.
 * @return {number} The product of a and b.
 */
o3djs.math.mulScalarScalar = function(a, b) {
  return a * b;
};

/**
 * Multiplies a scalar by a vector.
 * @param {number} k The scalar.
 * @param {!o3djs.math.Vector} v The vector.
 * @return {!o3djs.math.Vector} The product of k and v.
 */
o3djs.math.mulScalarVector = function(k, v) {
  var r = [];
  var vLength = v.length;
  for (var i = 0; i < vLength; ++i) {
    r[i] = k * v[i];
  }
  return r;
};

/**
 * Multiplies a vector by a scalar.
 * @param {!o3djs.math.Vector} v The vector.
 * @param {number} k The scalar.
 * @return {!o3djs.math.Vector} The product of k and v.
 */
o3djs.math.mulVectorScalar = function(v, k) {
  return o3djs.math.mulScalarVector(k, v);
};

/**
 * Multiplies a scalar by a matrix.
 * @param {number} k The scalar.
 * @param {!o3djs.math.Matrix} m The matrix.
 * @return {!o3djs.math.Matrix} The product of m and k.
 */
o3djs.math.mulScalarMatrix = function(k, m) {
  var r = [];
  var mLength = m.length;
  var m0Length = m[0].length;
  for (var i = 0; i < mLength; ++i) {
    r[i] = [];
    for (var j = 0; j < m0Length; ++j)
      r[i][j] = k * m[i][j];
  }
  return r;
};

/**
 * Multiplies a matrix by a scalar.
 * @param {!o3djs.math.Matrix} m The matrix.
 * @param {number} k The scalar.
 * @return {!o3djs.math.Matrix} The product of m and k.
 */
o3djs.math.mulMatrixScalar = function(m, k) {
  return o3djs.math.mulScalarMatrix(k, m);
};

/**
 * Multiplies a vector by another vector (component-wise); assumes a and
 * b have the same length.
 * @param {!o3djs.math.Vector} a Operand vector.
 * @param {!o3djs.math.Vector} b Operand vector.
 * @return {!o3djs.math.Vector} The vector of products of entries of a and
 *     b.
 */
o3djs.math.mulVectorVector = function(a, b) {
  var r = [];
  var aLength = a.length;
  for (var i = 0; i < aLength; ++i)
    r[i] = a[i] * b[i];
  return r;
};

/**
 * Divides a vector by another vector (component-wise); assumes a and
 * b have the same length.
 * @param {!o3djs.math.Vector} a Operand vector.
 * @param {!o3djs.math.Vector} b Operand vector.
 * @return {!o3djs.math.Vector} The vector of quotients of entries of a and
 *     b.
 */
o3djs.math.divVectorVector = function(a, b) {
  var r = [];
  var aLength = a.length;
  for (var i = 0; i < aLength; ++i)
    r[i] = a[i] / b[i];
  return r;
};

/**
 * Multiplies a vector by a matrix; treats the vector as a row vector; assumes
 * matrix entries are accessed in [row][column] fashion.
 * @param {!o3djs.math.Vector} v The vector.
 * @param {!o3djs.math.Matrix} m The matrix.
 * @return {!o3djs.math.Vector} The product of v and m as a row vector.
 */
o3djs.math.rowMajor.mulVectorMatrix = function(v, m) {
  var r = [];
  var m0Length = m[0].length;
  var vLength = v.length;
  for (var i = 0; i < m0Length; ++i) {
    r[i] = 0.0;
    for (var j = 0; j < vLength; ++j)
      r[i] += v[j] * m[j][i];
  }
  return r;
};

/**
 * Multiplies a vector by a matrix; treats the vector as a row vector; assumes
 * matrix entries are accessed in [column][row] fashion.
 * @param {!o3djs.math.Vector} v The vector.
 * @param {!o3djs.math.Matrix} m The matrix.
 * @return {!o3djs.math.Vector} The product of v and m as a row vector.
 */
o3djs.math.columnMajor.mulVectorMatrix = function(v, m) {
  var r = [];
  var mLength = m.length;
  var vLength = v.length;
  for (var i = 0; i < mLength; ++i) {
    r[i] = 0.0;
    var column = m[i];
    for (var j = 0; j < vLength; ++j)
      r[i] += v[j] * column[j];
  }
  return r;
};

/**
 * Multiplies a vector by a matrix; treats the vector as a row vector.
 * @param {!o3djs.math.Matrix} m The matrix.
 * @param {!o3djs.math.Vector} v The vector.
 * @return {!o3djs.math.Vector} The product of m and v as a row vector.
 */
o3djs.math.mulVectorMatrix = null;

/**
 * Multiplies a matrix by a vector; treats the vector as a column vector.
 * assumes matrix entries are accessed in [row][column] fashion.
 * @param {!o3djs.math.Matrix} m The matrix.
 * @param {!o3djs.math.Vector} v The vector.
 * @return {!o3djs.math.Vector} The product of m and v as a column vector.
 */
o3djs.math.rowMajor.mulMatrixVector = function(m, v) {
  var r = [];
  var mLength = m.length;
  var m0Length = m[0].length;
  for (var i = 0; i < mLength; ++i) {
    r[i] = 0.0;
    var row = m[i];
    for (var j = 0; j < m0Length; ++j)
      r[i] += row[j] * v[j];
  }
  return r;
};

/**
 * Multiplies a matrix by a vector; treats the vector as a column vector;
 * assumes matrix entries are accessed in [column][row] fashion.
 * @param {!o3djs.math.Matrix} m The matrix.
 * @param {!o3djs.math.Vector} v The vector.
 * @return {!o3djs.math.Vector} The product of m and v as a column vector.
 */
o3djs.math.columnMajor.mulMatrixVector = function(m, v) {
  var r = [];
  var m0Length = m[0].length;
  var vLength = v.length;
  for (var i = 0; i < m0Length; ++i) {
    r[i] = 0.0;
    for (var j = 0; j < vLength; ++j)
      r[i] += v[j] * m[j][i];
  }
  return r;
};

/**
 * Multiplies a matrix by a vector; treats the vector as a column vector.
 * @param {!o3djs.math.Matrix} m The matrix.
 * @param {!o3djs.math.Vector} v The vector.
 * @return {!o3djs.math.Vector} The product of m and v as a column vector.
 */
o3djs.math.mulMatrixVector = null;

/**
 * Multiplies two 2-by-2 matrices; assumes that the given matrices are 2-by-2;
 * assumes matrix entries are accessed in [row][column] fashion.
 * @param {!o3djs.math.Matrix2} a The matrix on the left.
 * @param {!o3djs.math.Matrix2} b The matrix on the right.
 * @return {!o3djs.math.Matrix2} The matrix product of a and b.
 */
o3djs.math.rowMajor.mulMatrixMatrix2 = function(a, b) {
  var a0 = a[0];
  var a1 = a[1];
  var b0 = b[0];
  var b1 = b[1];
  var a00 = a0[0];
  var a01 = a0[1];
  var a10 = a1[0];
  var a11 = a1[1];
  var b00 = b0[0];
  var b01 = b0[1];
  var b10 = b1[0];
  var b11 = b1[1];
  return [[a00 * b00 + a01 * b10, a00 * b01 + a01 * b11],
          [a10 * b00 + a11 * b10, a10 * b01 + a11 * b11]];
};

/**
 * Multiplies two 2-by-2 matrices; assumes that the given matrices are 2-by-2;
 * assumes matrix entries are accessed in [column][row] fashion.
 * @param {!o3djs.math.Matrix2} a The matrix on the left.
 * @param {!o3djs.math.Matrix2} b The matrix on the right.
 * @return {!o3djs.math.Matrix2} The matrix product of a and b.
 */
o3djs.math.columnMajor.mulMatrixMatrix2 = function(a, b) {
  var a0 = a[0];
  var a1 = a[1];
  var b0 = b[0];
  var b1 = b[1];
  var a00 = a0[0];
  var a01 = a0[1];
  var a10 = a1[0];
  var a11 = a1[1];
  var b00 = b0[0];
  var b01 = b0[1];
  var b10 = b1[0];
  var b11 = b1[1];
  return [[a00 * b00 + a10 * b01, a01 * b00 + a11 * b01],
          [a00 * b10 + a10 * b11, a01 * b10 + a11 * b11]];
};

/**
 * Multiplies two 2-by-2 matrices.
 * @param {!o3djs.math.Matrix2} a The matrix on the left.
 * @param {!o3djs.math.Matrix2} b The matrix on the right.
 * @return {!o3djs.math.Matrix2} The matrix product of a and b.
 */
o3djs.math.mulMatrixMatrix2 = null;


/**
 * Multiplies two 3-by-3 matrices; assumes that the given matrices are 3-by-3;
 * assumes matrix entries are accessed in [row][column] fashion.
 * @param {!o3djs.math.Matrix3} a The matrix on the left.
 * @param {!o3djs.math.Matrix3} b The matrix on the right.
 * @return {!o3djs.math.Matrix3} The matrix product of a and b.
 */
o3djs.math.rowMajor.mulMatrixMatrix3 = function(a, b) {
  var a0 = a[0];
  var a1 = a[1];
  var a2 = a[2];
  var b0 = b[0];
  var b1 = b[1];
  var b2 = b[2];
  var a00 = a0[0];
  var a01 = a0[1];
  var a02 = a0[2];
  var a10 = a1[0];
  var a11 = a1[1];
  var a12 = a1[2];
  var a20 = a2[0];
  var a21 = a2[1];
  var a22 = a2[2];
  var b00 = b0[0];
  var b01 = b0[1];
  var b02 = b0[2];
  var b10 = b1[0];
  var b11 = b1[1];
  var b12 = b1[2];
  var b20 = b2[0];
  var b21 = b2[1];
  var b22 = b2[2];
  return [[a00 * b00 + a01 * b10 + a02 * b20,
           a00 * b01 + a01 * b11 + a02 * b21,
           a00 * b02 + a01 * b12 + a02 * b22],
          [a10 * b00 + a11 * b10 + a12 * b20,
           a10 * b01 + a11 * b11 + a12 * b21,
           a10 * b02 + a11 * b12 + a12 * b22],
          [a20 * b00 + a21 * b10 + a22 * b20,
           a20 * b01 + a21 * b11 + a22 * b21,
           a20 * b02 + a21 * b12 + a22 * b22]];
};

/**
 * Multiplies two 3-by-3 matrices; assumes that the given matrices are 3-by-3;
 * assumes matrix entries are accessed in [column][row] fashion.
 * @param {!o3djs.math.Matrix3} a The matrix on the left.
 * @param {!o3djs.math.Matrix3} b The matrix on the right.
 * @return {!o3djs.math.Matrix3} The matrix product of a and b.
 */
o3djs.math.columnMajor.mulMatrixMatrix3 = function(a, b) {
  var a0 = a[0];
  var a1 = a[1];
  var a2 = a[2];
  var b0 = b[0];
  var b1 = b[1];
  var b2 = b[2];
  var a00 = a0[0];
  var a01 = a0[1];
  var a02 = a0[2];
  var a10 = a1[0];
  var a11 = a1[1];
  var a12 = a1[2];
  var a20 = a2[0];
  var a21 = a2[1];
  var a22 = a2[2];
  var b00 = b0[0];
  var b01 = b0[1];
  var b02 = b0[2];
  var b10 = b1[0];
  var b11 = b1[1];
  var b12 = b1[2];
  var b20 = b2[0];
  var b21 = b2[1];
  var b22 = b2[2];
  return [[a00 * b00 + a10 * b01 + a20 * b02,
           a01 * b00 + a11 * b01 + a21 * b02,
           a02 * b00 + a12 * b01 + a22 * b02],
          [a00 * b10 + a10 * b11 + a20 * b12,
           a01 * b10 + a11 * b11 + a21 * b12,
           a02 * b10 + a12 * b11 + a22 * b12],
          [a00 * b20 + a10 * b21 + a20 * b22,
           a01 * b20 + a11 * b21 + a21 * b22,
           a02 * b20 + a12 * b21 + a22 * b22]];
};

/**
 * Multiplies two 3-by-3 matrices; assumes that the given matrices are 3-by-3.
 * @param {!o3djs.math.Matrix3} a The matrix on the left.
 * @param {!o3djs.math.Matrix3} b The matrix on the right.
 * @return {!o3djs.math.Matrix3} The matrix product of a and b.
 */
o3djs.math.mulMatrixMatrix3 = null;

/**
 * Multiplies two 4-by-4 matrices; assumes that the given matrices are 4-by-4;
 * assumes matrix entries are accessed in [row][column] fashion.
 * @param {!o3djs.math.Matrix4} a The matrix on the left.
 * @param {!o3djs.math.Matrix4} b The matrix on the right.
 * @return {!o3djs.math.Matrix4} The matrix product of a and b.
 */
o3djs.math.rowMajor.mulMatrixMatrix4 = function(a, b) {
  var a0 = a[0];
  var a1 = a[1];
  var a2 = a[2];
  var a3 = a[3];
  var b0 = b[0];
  var b1 = b[1];
  var b2 = b[2];
  var b3 = b[3];
  var a00 = a0[0];
  var a01 = a0[1];
  var a02 = a0[2];
  var a03 = a0[3];
  var a10 = a1[0];
  var a11 = a1[1];
  var a12 = a1[2];
  var a13 = a1[3];
  var a20 = a2[0];
  var a21 = a2[1];
  var a22 = a2[2];
  var a23 = a2[3];
  var a30 = a3[0];
  var a31 = a3[1];
  var a32 = a3[2];
  var a33 = a3[3];
  var b00 = b0[0];
  var b01 = b0[1];
  var b02 = b0[2];
  var b03 = b0[3];
  var b10 = b1[0];
  var b11 = b1[1];
  var b12 = b1[2];
  var b13 = b1[3];
  var b20 = b2[0];
  var b21 = b2[1];
  var b22 = b2[2];
  var b23 = b2[3];
  var b30 = b3[0];
  var b31 = b3[1];
  var b32 = b3[2];
  var b33 = b3[3];
  return [[a00 * b00 + a01 * b10 + a02 * b20 + a03 * b30,
           a00 * b01 + a01 * b11 + a02 * b21 + a03 * b31,
           a00 * b02 + a01 * b12 + a02 * b22 + a03 * b32,
           a00 * b03 + a01 * b13 + a02 * b23 + a03 * b33],
          [a10 * b00 + a11 * b10 + a12 * b20 + a13 * b30,
           a10 * b01 + a11 * b11 + a12 * b21 + a13 * b31,
           a10 * b02 + a11 * b12 + a12 * b22 + a13 * b32,
           a10 * b03 + a11 * b13 + a12 * b23 + a13 * b33],
          [a20 * b00 + a21 * b10 + a22 * b20 + a23 * b30,
           a20 * b01 + a21 * b11 + a22 * b21 + a23 * b31,
           a20 * b02 + a21 * b12 + a22 * b22 + a23 * b32,
           a20 * b03 + a21 * b13 + a22 * b23 + a23 * b33],
          [a30 * b00 + a31 * b10 + a32 * b20 + a33 * b30,
           a30 * b01 + a31 * b11 + a32 * b21 + a33 * b31,
           a30 * b02 + a31 * b12 + a32 * b22 + a33 * b32,
           a30 * b03 + a31 * b13 + a32 * b23 + a33 * b33]];
};

/**
 * Multiplies two 4-by-4 matrices; assumes that the given matrices are 4-by-4;
 * assumes matrix entries are accessed in [column][row] fashion.
 * @param {!o3djs.math.Matrix4} a The matrix on the left.
 * @param {!o3djs.math.Matrix4} b The matrix on the right.
 * @return {!o3djs.math.Matrix4} The matrix product of a and b.
 */
o3djs.math.columnMajor.mulMatrixMatrix4 = function(a, b) {
  var a0 = a[0];
  var a1 = a[1];
  var a2 = a[2];
  var a3 = a[3];
  var b0 = b[0];
  var b1 = b[1];
  var b2 = b[2];
  var b3 = b[3];
  var a00 = a0[0];
  var a01 = a0[1];
  var a02 = a0[2];
  var a03 = a0[3];
  var a10 = a1[0];
  var a11 = a1[1];
  var a12 = a1[2];
  var a13 = a1[3];
  var a20 = a2[0];
  var a21 = a2[1];
  var a22 = a2[2];
  var a23 = a2[3];
  var a30 = a3[0];
  var a31 = a3[1];
  var a32 = a3[2];
  var a33 = a3[3];
  var b00 = b0[0];
  var b01 = b0[1];
  var b02 = b0[2];
  var b03 = b0[3];
  var b10 = b1[0];
  var b11 = b1[1];
  var b12 = b1[2];
  var b13 = b1[3];
  var b20 = b2[0];
  var b21 = b2[1];
  var b22 = b2[2];
  var b23 = b2[3];
  var b30 = b3[0];
  var b31 = b3[1];
  var b32 = b3[2];
  var b33 = b3[3];
  return [[a00 * b00 + a10 * b01 + a20 * b02 + a30 * b03,
           a01 * b00 + a11 * b01 + a21 * b02 + a31 * b03,
           a02 * b00 + a12 * b01 + a22 * b02 + a32 * b03,
           a03 * b00 + a13 * b01 + a23 * b02 + a33 * b03],
          [a00 * b10 + a10 * b11 + a20 * b12 + a30 * b13,
           a01 * b10 + a11 * b11 + a21 * b12 + a31 * b13,
           a02 * b10 + a12 * b11 + a22 * b12 + a32 * b13,
           a03 * b10 + a13 * b11 + a23 * b12 + a33 * b13],
          [a00 * b20 + a10 * b21 + a20 * b22 + a30 * b23,
           a01 * b20 + a11 * b21 + a21 * b22 + a31 * b23,
           a02 * b20 + a12 * b21 + a22 * b22 + a32 * b23,
           a03 * b20 + a13 * b21 + a23 * b22 + a33 * b23],
          [a00 * b30 + a10 * b31 + a20 * b32 + a30 * b33,
           a01 * b30 + a11 * b31 + a21 * b32 + a31 * b33,
           a02 * b30 + a12 * b31 + a22 * b32 + a32 * b33,
           a03 * b30 + a13 * b31 + a23 * b32 + a33 * b33]];
};

/**
 * Multiplies two 4-by-4 matrices; assumes that the given matrices are 4-by-4.
 * @param {!o3djs.math.Matrix4} a The matrix on the left.
 * @param {!o3djs.math.Matrix4} b The matrix on the right.
 * @return {!o3djs.math.Matrix4} The matrix product of a and b.
 */
o3djs.math.mulMatrixMatrix4 = null;

/**
 * Multiplies two matrices; assumes that the sizes of the matrices are
 * appropriately compatible; assumes matrix entries are accessed in
 * [row][column] fashion.
 * @param {!o3djs.math.Matrix} a The matrix on the left.
 * @param {!o3djs.math.Matrix} b The matrix on the right.
 * @return {!o3djs.math.Matrix} The matrix product of a and b.
 */
o3djs.math.rowMajor.mulMatrixMatrix = function(a, b) {
  var r = [];
  var aRows = a.length;
  var bColumns = b[0].length;
  var bRows = b.length;
  for (var i = 0; i < aRows; ++i) {
    var v = [];    // v becomes a row of the answer.
    var ai = a[i]; // ith row of a.
    for (var j = 0; j < bColumns; ++j) {
      v[j] = 0.0;
      for (var k = 0; k < bRows; ++k)
        v[j] += ai[k] * b[k][j]; // kth row, jth column.
    }
    r[i] = v;
  }
  return r;
};

/**
 * Multiplies two matrices; assumes that the sizes of the matrices are
 * appropriately compatible; assumes matrix entries are accessed in
 * [row][column] fashion.
 * @param {!o3djs.math.Matrix} a The matrix on the left.
 * @param {!o3djs.math.Matrix} b The matrix on the right.
 * @return {!o3djs.math.Matrix} The matrix product of a and b.
 */
o3djs.math.columnMajor.mulMatrixMatrix = function(a, b) {
  var r = [];
  var bColumns = b.length;
  var aRows = a[0].length;
  var aColumns = a.length;
  for (var i = 0; i < bColumns; ++i) {
    var v = [];    // v becomes a column of the answer.
    var bi = b[i]; // ith column of b.
    for (var j = 0; j < aRows; ++j) {
      v[j] = 0.0;
      for (var k = 0; k < aColumns; ++k)
        v[j] += bi[k] * a[k][j]; // kth column, jth row.
    }
    r[i] = v;
  }
  return r;
};

/**
 * Multiplies two matrices; assumes that the sizes of the matrices are
 * appropriately compatible.
 * @param {!o3djs.math.Matrix} a The matrix on the left.
 * @param {!o3djs.math.Matrix} b The matrix on the right.
 * @return {!o3djs.math.Matrix} The matrix product of a and b.
 */
o3djs.math.mulMatrixMatrix = null;

/**
 * Gets the jth column of the given matrix m; assumes matrix entries are
 * accessed in [row][column] fashion.
 * @param {!o3djs.math.Matrix} m The matrix.
 * @param {number} j The index of the desired column.
 * @return {!o3djs.math.Vector} The jth column of m as a vector.
 */
o3djs.math.rowMajor.column = function(m, j) {
  var r = [];
  var mLength = m.length;
  for (var i = 0; i < mLength; ++i) {
    r[i] = m[i][j];
  }
  return r;
};

/**
 * Gets the jth column of the given matrix m; assumes matrix entries are
 * accessed in [column][row] fashion.
 * @param {!o3djs.math.Matrix} m The matrix.
 * @param {number} j The index of the desired column.
 * @return {!o3djs.math.Vector} The jth column of m as a vector.
 */
o3djs.math.columnMajor.column = function(m, j) {
  return m[j].slice();
};

/**
 * Gets the jth column of the given matrix m.
 * @param {!o3djs.math.Matrix} m The matrix.
 * @param {number} j The index of the desired column.
 * @return {!o3djs.math.Vector} The jth column of m as a vector.
 */
o3djs.math.column = null;

/**
 * Gets the ith row of the given matrix m; assumes matrix entries are
 * accessed in [row][column] fashion.
 * @param {!o3djs.math.Matrix} m The matrix.
 * @param {number} i The index of the desired row.
 * @return {!o3djs.math.Vector} The ith row of m.
 */
o3djs.math.rowMajor.row = function(m, i) {
  return m[i].slice();
};

/**
 * Gets the ith row of the given matrix m; assumes matrix entries are
 * accessed in [column][row] fashion.
 * @param {!o3djs.math.Matrix} m The matrix.
 * @param {number} i The index of the desired row.
 * @return {!o3djs.math.Vector} The ith row of m.
 */
o3djs.math.columnMajor.row = function(m, i) {
  var r = [];
  var mLength = m.length;
  for (var j = 0; j < mLength; ++j) {
    r[j] = m[j][i];
  }
  return r;
};

/**
 * Gets the ith row of the given matrix m.
 * @param {!o3djs.math.Matrix} m The matrix.
 * @param {number} i The index of the desired row.
 * @return {!o3djs.math.Vector} The ith row of m.
 */
o3djs.math.row = null;

/**
 * Creates an n-by-n identity matrix.
 * @param {number} n The dimension of the identity matrix required.
 * @return {!o3djs.math.Matrix} An n-by-n identity matrix.
 */
o3djs.math.identity = function(n) {
  var r = [];
  for (var j = 0; j < n; ++j) {
    r[j] = [];
    for (var i = 0; i < n; ++i)
      r[j][i] = (i == j) ? 1 : 0;
  }
  return r;
};

/**
 * Takes the transpose of a matrix.
 * @param {!o3djs.math.Matrix} m The matrix.
 * @return {!o3djs.math.Matrix} The transpose of m.
 */
o3djs.math.transpose = function(m) {
  var r = [];
  var m0Length = m[0].length;
  var mLength = m.length;
  for (var j = 0; j < m0Length; ++j) {
    r[j] = [];
    for (var i = 0; i < mLength; ++i)
      r[j][i] = m[i][j];
  }
  return r;
};

/**
 * Computes the trace (sum of the diagonal entries) of a square matrix;
 * assumes m is square.
 * @param {!o3djs.math.Matrix} m The matrix.
 * @return {number} The trace of m.
 */
o3djs.math.trace = function(m) {
  var r = 0.0;
  var mLength = m.length;
  for (var i = 0; i < mLength; ++i)
    r += m[i][i];
  return r;
};

/**
 * Computes the determinant of a 1-by-1 matrix.
 * @param {!o3djs.math.Matrix1} m The matrix.
 * @return {number} The determinant of m.
 */
o3djs.math.det1 = function(m) {
  return m[0][0];
};

/**
 * Computes the determinant of a 2-by-2 matrix.
 * @param {!o3djs.math.Matrix2} m The matrix.
 * @return {number} The determinant of m.
 */
o3djs.math.det2 = function(m) {
  return m[0][0] * m[1][1] - m[0][1] * m[1][0];
};

/**
 * Computes the determinant of a 3-by-3 matrix.
 * @param {!o3djs.math.Matrix3} m The matrix.
 * @return {number} The determinant of m.
 */
o3djs.math.det3 = function(m) {
  return m[2][2] * (m[0][0] * m[1][1] - m[0][1] * m[1][0]) -
         m[2][1] * (m[0][0] * m[1][2] - m[0][2] * m[1][0]) +
         m[2][0] * (m[0][1] * m[1][2] - m[0][2] * m[1][1]);
};

/**
 * Computes the determinant of a 4-by-4 matrix.
 * @param {!o3djs.math.Matrix4} m The matrix.
 * @return {number} The determinant of m.
 */
o3djs.math.det4 = function(m) {
  var t01 = m[0][0] * m[1][1] - m[0][1] * m[1][0];
  var t02 = m[0][0] * m[1][2] - m[0][2] * m[1][0];
  var t03 = m[0][0] * m[1][3] - m[0][3] * m[1][0];
  var t12 = m[0][1] * m[1][2] - m[0][2] * m[1][1];
  var t13 = m[0][1] * m[1][3] - m[0][3] * m[1][1];
  var t23 = m[0][2] * m[1][3] - m[0][3] * m[1][2];
  return m[3][3] * (m[2][2] * t01 - m[2][1] * t02 + m[2][0] * t12) -
         m[3][2] * (m[2][3] * t01 - m[2][1] * t03 + m[2][0] * t13) +
         m[3][1] * (m[2][3] * t02 - m[2][2] * t03 + m[2][0] * t23) -
         m[3][0] * (m[2][3] * t12 - m[2][2] * t13 + m[2][1] * t23);
};

/**
 * Computes the inverse of a 1-by-1 matrix.
 * @param {!o3djs.math.Matrix1} m The matrix.
 * @return {!o3djs.math.Matrix1} The inverse of m.
 */
o3djs.math.inverse1 = function(m) {
  return [[1.0 / m[0][0]]];
};

/**
 * Computes the inverse of a 2-by-2 matrix.
 * @param {!o3djs.math.Matrix2} m The matrix.
 * @return {!o3djs.math.Matrix2} The inverse of m.
 */
o3djs.math.inverse2 = function(m) {
  var d = 1.0 / (m[0][0] * m[1][1] - m[0][1] * m[1][0]);
  return [[d * m[1][1], -d * m[0][1]], [-d * m[1][0], d * m[0][0]]];
};

/**
 * Computes the inverse of a 3-by-3 matrix.
 * @param {!o3djs.math.Matrix3} m The matrix.
 * @return {!o3djs.math.Matrix3} The inverse of m.
 */
o3djs.math.inverse3 = function(m) {
  var t00 = m[1][1] * m[2][2] - m[1][2] * m[2][1];
  var t10 = m[0][1] * m[2][2] - m[0][2] * m[2][1];
  var t20 = m[0][1] * m[1][2] - m[0][2] * m[1][1];
  var d = 1.0 / (m[0][0] * t00 - m[1][0] * t10 + m[2][0] * t20);
  return [[d * t00, -d * t10, d * t20],
          [-d * (m[1][0] * m[2][2] - m[1][2] * m[2][0]),
            d * (m[0][0] * m[2][2] - m[0][2] * m[2][0]),
           -d * (m[0][0] * m[1][2] - m[0][2] * m[1][0])],
          [d * (m[1][0] * m[2][1] - m[1][1] * m[2][0]),
          -d * (m[0][0] * m[2][1] - m[0][1] * m[2][0]),
           d * (m[0][0] * m[1][1] - m[0][1] * m[1][0])]];
};

/**
 * Computes the inverse of a 4-by-4 matrix.
 * @param {!o3djs.math.Matrix4} m The matrix.
 * @return {!o3djs.math.Matrix4} The inverse of m.
 */
o3djs.math.inverse4 = function(m) {
  var tmp_0 = m[2][2] * m[3][3];
  var tmp_1 = m[3][2] * m[2][3];
  var tmp_2 = m[1][2] * m[3][3];
  var tmp_3 = m[3][2] * m[1][3];
  var tmp_4 = m[1][2] * m[2][3];
  var tmp_5 = m[2][2] * m[1][3];
  var tmp_6 = m[0][2] * m[3][3];
  var tmp_7 = m[3][2] * m[0][3];
  var tmp_8 = m[0][2] * m[2][3];
  var tmp_9 = m[2][2] * m[0][3];
  var tmp_10 = m[0][2] * m[1][3];
  var tmp_11 = m[1][2] * m[0][3];
  var tmp_12 = m[2][0] * m[3][1];
  var tmp_13 = m[3][0] * m[2][1];
  var tmp_14 = m[1][0] * m[3][1];
  var tmp_15 = m[3][0] * m[1][1];
  var tmp_16 = m[1][0] * m[2][1];
  var tmp_17 = m[2][0] * m[1][1];
  var tmp_18 = m[0][0] * m[3][1];
  var tmp_19 = m[3][0] * m[0][1];
  var tmp_20 = m[0][0] * m[2][1];
  var tmp_21 = m[2][0] * m[0][1];
  var tmp_22 = m[0][0] * m[1][1];
  var tmp_23 = m[1][0] * m[0][1];

  var t0 = (tmp_0 * m[1][1] + tmp_3 * m[2][1] + tmp_4 * m[3][1]) -
      (tmp_1 * m[1][1] + tmp_2 * m[2][1] + tmp_5 * m[3][1]);
  var t1 = (tmp_1 * m[0][1] + tmp_6 * m[2][1] + tmp_9 * m[3][1]) -
      (tmp_0 * m[0][1] + tmp_7 * m[2][1] + tmp_8 * m[3][1]);
  var t2 = (tmp_2 * m[0][1] + tmp_7 * m[1][1] + tmp_10 * m[3][1]) -
      (tmp_3 * m[0][1] + tmp_6 * m[1][1] + tmp_11 * m[3][1]);
  var t3 = (tmp_5 * m[0][1] + tmp_8 * m[1][1] + tmp_11 * m[2][1]) -
      (tmp_4 * m[0][1] + tmp_9 * m[1][1] + tmp_10 * m[2][1]);

  var d = 1.0 / (m[0][0] * t0 + m[1][0] * t1 + m[2][0] * t2 + m[3][0] * t3);

  var row0 = [d * t0, d * t1, d * t2, d * t3];
  var row1 = [d * ((tmp_1 * m[1][0] + tmp_2 * m[2][0] + tmp_5 * m[3][0]) -
          (tmp_0 * m[1][0] + tmp_3 * m[2][0] + tmp_4 * m[3][0])),
       d * ((tmp_0 * m[0][0] + tmp_7 * m[2][0] + tmp_8 * m[3][0]) -
          (tmp_1 * m[0][0] + tmp_6 * m[2][0] + tmp_9 * m[3][0])),
       d * ((tmp_3 * m[0][0] + tmp_6 * m[1][0] + tmp_11 * m[3][0]) -
          (tmp_2 * m[0][0] + tmp_7 * m[1][0] + tmp_10 * m[3][0])),
       d * ((tmp_4 * m[0][0] + tmp_9 * m[1][0] + tmp_10 * m[2][0]) -
          (tmp_5 * m[0][0] + tmp_8 * m[1][0] + tmp_11 * m[2][0]))];
  var row2 =[d * ((tmp_12 * m[1][3] + tmp_15 * m[2][3] + tmp_16 * m[3][3]) -
          (tmp_13 * m[1][3] + tmp_14 * m[2][3] + tmp_17 * m[3][3])),
       d * ((tmp_13 * m[0][3] + tmp_18 * m[2][3] + tmp_21 * m[3][3]) -
          (tmp_12 * m[0][3] + tmp_19 * m[2][3] + tmp_20 * m[3][3])),
       d * ((tmp_14 * m[0][3] + tmp_19 * m[1][3] + tmp_22 * m[3][3]) -
          (tmp_15 * m[0][3] + tmp_18 * m[1][3] + tmp_23 * m[3][3])),
       d * ((tmp_17 * m[0][3] + tmp_20 * m[1][3] + tmp_23 * m[2][3]) -
          (tmp_16 * m[0][3] + tmp_21 * m[1][3] + tmp_22 * m[2][3]))];
  var row3 = [d * ((tmp_14 * m[2][2] + tmp_17 * m[3][2] + tmp_13 * m[1][2]) -
          (tmp_16 * m[3][2] + tmp_12 * m[1][2] + tmp_15 * m[2][2])),
       d * ((tmp_20 * m[3][2] + tmp_12 * m[0][2] + tmp_19 * m[2][2]) -
          (tmp_18 * m[2][2] + tmp_21 * m[3][2] + tmp_13 * m[0][2])),
       d * ((tmp_18 * m[1][2] + tmp_23 * m[3][2] + tmp_15 * m[0][2]) -
          (tmp_22 * m[3][2] + tmp_14 * m[0][2] + tmp_19 * m[1][2])),
       d * ((tmp_22 * m[2][2] + tmp_16 * m[0][2] + tmp_21 * m[1][2]) -
          (tmp_20 * m[1][2] + tmp_23 * m[2][2] + tmp_17 * m[0][2]))];
  return [row0, row1, row2, row3];
};

/**
 * Computes the determinant of the cofactor matrix obtained by removal
 * of a specified row and column.  This is a helper function for the general
 * determinant and matrix inversion functions.
 * @param {!o3djs.math.Matrix} a The original matrix.
 * @param {number} x The row to be removed.
 * @param {number} y The column to be removed.
 * @return {number} The determinant of the matrix obtained by removing
 *     row x and column y from a.
 */
o3djs.math.codet = function(a, x, y) {
  var size = a.length;
  var b = [];
  var ai = 0;
  for (var bi = 0; bi < size - 1; ++bi) {
    if (ai == x)
      ai++;
    b[bi] = [];
    var aj = 0;
    for (var bj = 0; bj < size - 1; ++bj) {
      if (aj == y)
        aj++;
      b[bi][bj] = a[ai][aj];
      aj++;
    }
    ai++;
  }
  return o3djs.math.det(b);
};

/**
 * Computes the determinant of an arbitrary square matrix.
 * @param {!o3djs.math.Matrix} m The matrix.
 * @return {number} the determinant of m.
 */
o3djs.math.det = function(m) {
  var d = m.length;
  if (d <= 4) {
    return o3djs.math['det' + d](m);
  }
  var r = 0.0;
  var sign = 1;
  var row = m[0];
  var mLength = m.length;
  for (var y = 0; y < mLength; y++) {
    r += sign * row[y] * o3djs.math.codet(m, 0, y);
    sign *= -1;
  }
  return r;
};

/**
 * Computes the inverse of an arbitrary square matrix.
 * @param {!o3djs.math.Matrix} m The matrix.
 * @return {!o3djs.math.Matrix} The inverse of m.
 */
o3djs.math.inverse = function(m) {
  var d = m.length;
  if (d <= 4) {
    return o3djs.math['inverse' + d](m);
  }
  var r = [];
  var size = m.length;
  for (var j = 0; j < size; ++j) {
    r[j] = [];
    for (var i = 0; i < size; ++i)
      r[j][i] = ((i + j) % 2 ? -1 : 1) * o3djs.math.codet(m, i, j);
  }
  return o3djs.math.divMatrixScalar(r, o3djs.math.det(m));
};

/**
 * Performs Graham-Schmidt orthogonalization on the vectors which make up the
 * given matrix and returns the result in the rows of a new matrix.  When
 * multiplying many orthogonal matrices together, errors can accumulate causing
 * the product to fail to be orthogonal.  This function can be used to correct
 * that.
 * @param {!o3djs.math.Matrix} m The matrix.
 * @return {!o3djs.math.Matrix} A matrix whose rows are obtained from the
 *     rows of m by the Graham-Schmidt process.
 */
o3djs.math.orthonormalize = function(m) {
  var r = [];
  var mLength = m.length;
  for (var i = 0; i < mLength; ++i) {
    var v = m[i];
    for (var j = 0; j < i; ++j) {
      v = o3djs.math.subVector(v, o3djs.math.mulScalarVector(
          o3djs.math.dot(r[j], m[i]), r[j]));
    }
    r[i] = o3djs.math.normalize(v);
  }
  return r;
};

/**
 * Computes the inverse of a 4-by-4 matrix.
 * Note: It is faster to call this than o3djs.math.inverse.
 * @param {!o3djs.math.Matrix4} m The matrix.
 * @return {!o3djs.math.Matrix4} The inverse of m.
 */
o3djs.math.matrix4.inverse = function(m) {
  return o3djs.math.inverse4(m);
};

/**
 * Multiplies two 4-by-4 matrices; assumes that the given matrices are 4-by-4.
 * Note: It is faster to call this than o3djs.math.mul.
 * @param {!o3djs.math.Matrix4} a The matrix on the left.
 * @param {!o3djs.math.Matrix4} b The matrix on the right.
 * @return {!o3djs.math.Matrix4} The matrix product of a and b.
 */
o3djs.math.matrix4.mul = function(a, b) {
  return o3djs.math.mulMatrixMatrix4(a, b);
};

/**
 * Computes the determinant of a 4-by-4 matrix.
 * Note: It is faster to call this than o3djs.math.det.
 * @param {!o3djs.math.Matrix4} m The matrix.
 * @return {number} The determinant of m.
 */
o3djs.math.matrix4.det = function(m) {
  return o3djs.math.det4(m);
};

/**
 * Copies a Matrix4.
 * Note: It is faster to call this than o3djs.math.copy.
 * @param {!o3djs.math.Matrix4} m The matrix.
 * @return {!o3djs.math.Matrix4} A copy of m.
 */
o3djs.math.matrix4.copy = function(m) {
  return o3djs.math.copyMatrix(m);
};

/**
 * Sets the upper 3-by-3 block of matrix a to the upper 3-by-3 block of matrix
 * b; assumes that a and b are big enough to contain an upper 3-by-3 block.
 * @param {!o3djs.math.Matrix4} a A matrix.
 * @param {!o3djs.math.Matrix3} b A 3-by-3 matrix.
 * @return {!o3djs.math.Matrix4} a once modified.
 */
o3djs.math.matrix4.setUpper3x3 = function(a, b) {
  var b0 = b[0];
  var b1 = b[1];
  var b2 = b[2];

  a[0].splice(0, 3, b0[0], b0[1], b0[2]);
  a[1].splice(0, 3, b1[0], b1[1], b1[2]);
  a[2].splice(0, 3, b2[0], b2[1], b2[2]);

  return a;
};

/**
 * Returns a 3-by-3 matrix mimicking the upper 3-by-3 block of m; assumes m
 * is big enough to contain an upper 3-by-3 block.
 * @param {!o3djs.math.Matrix4} m The matrix.
 * @return {!o3djs.math.Matrix3} The upper 3-by-3 block of m.
 */
o3djs.math.matrix4.getUpper3x3 = function(m) {
  return [m[0].slice(0, 3), m[1].slice(0, 3), m[2].slice(0, 3)];
};

/**
 * Sets the translation component of a 4-by-4 matrix to the given
 * vector.
 * @param {!o3djs.math.Matrix4} a The matrix.
 * @param {(!o3djs.math.Vector3|!o3djs.math.Vector4)} v The vector.
 * @return {!o3djs.math.Matrix4} a once modified.
 */
o3djs.math.matrix4.setTranslation = function(a, v) {
  a[3].splice(0, 4, v[0], v[1], v[2], 1);
  return a;
};

/**
 * Returns the translation component of a 4-by-4 matrix as a vector with 3
 * entries.
 * @param {!o3djs.math.Matrix4} m The matrix.
 * @return {!o3djs.math.Vector3} The translation component of m.
 */
o3djs.math.matrix4.getTranslation = function(m) {
  return m[3].slice(0, 3);
};

/**
 * Takes a 4-by-4 matrix and a vector with 3 entries,
 * interprets the vector as a point, transforms that point by the matrix, and
 * returns the result as a vector with 3 entries.
 * @param {!o3djs.math.Matrix4} m The matrix.
 * @param {!o3djs.math.Vector3} v The point.
 * @return {!o3djs.math.Vector3} The transformed point.
 */
o3djs.math.matrix4.transformPoint = function(m, v) {
  var v0 = v[0];
  var v1 = v[1];
  var v2 = v[2];
  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];

  var d = v0 * m0[3] + v1 * m1[3] + v2 * m2[3] + m3[3];
  return [(v0 * m0[0] + v1 * m1[0] + v2 * m2[0] + m3[0]) / d,
          (v0 * m0[1] + v1 * m1[1] + v2 * m2[1] + m3[1]) / d,
          (v0 * m0[2] + v1 * m1[2] + v2 * m2[2] + m3[2]) / d];
};

/**
 * Takes a 4-by-4 matrix and a vector with 4 entries, transforms that vector by
 * the matrix, and returns the result as a vector with 4 entries.
 * @param {!o3djs.math.Matrix4} m The matrix.
 * @param {!o3djs.math.Vector4} v The point in homogenous coordinates.
 * @return {!o3djs.math.Vector4} The transformed point in homogenous
 *     coordinates.
 */
o3djs.math.matrix4.transformVector4 = function(m, v) {
  var v0 = v[0];
  var v1 = v[1];
  var v2 = v[2];
  var v3 = v[3];
  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];

  return [v0 * m0[0] + v1 * m1[0] + v2 * m2[0] + v3 * m3[0],
          v0 * m0[1] + v1 * m1[1] + v2 * m2[1] + v3 * m3[1],
          v0 * m0[2] + v1 * m1[2] + v2 * m2[2] + v3 * m3[2],
          v0 * m0[3] + v1 * m1[3] + v2 * m2[3] + v3 * m3[3]];
};

/**
 * Takes a 4-by-4 matrix and a vector with 3 entries, interprets the vector as a
 * direction, transforms that direction by the matrix, and returns the result;
 * assumes the transformation of 3-dimensional space represented by the matrix
 * is parallel-preserving, i.e. any combination of rotation, scaling and
 * translation, but not a perspective distortion. Returns a vector with 3
 * entries.
 * @param {!o3djs.math.Matrix4} m The matrix.
 * @param {!o3djs.math.Vector3} v The direction.
 * @return {!o3djs.math.Vector3} The transformed direction.
 */
o3djs.math.matrix4.transformDirection = function(m, v) {
  var v0 = v[0];
  var v1 = v[1];
  var v2 = v[2];
  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];

  return [v0 * m0[0] + v1 * m1[0] + v2 * m2[0],
          v0 * m0[1] + v1 * m1[1] + v2 * m2[1],
          v0 * m0[2] + v1 * m1[2] + v2 * m2[2]];
};

/**
 * Takes a 4-by-4 matrix m and a vector v with 3 entries, interprets the vector
 * as a normal to a surface, and computes a vector which is normal upon
 * transforming that surface by the matrix. The effect of this function is the
 * same as transforming v (as a direction) by the inverse-transpose of m.  This
 * function assumes the transformation of 3-dimensional space represented by the
 * matrix is parallel-preserving, i.e. any combination of rotation, scaling and
 * translation, but not a perspective distortion.  Returns a vector with 3
 * entries.
 * @param {!o3djs.math.Matrix4} m The matrix.
 * @param {!o3djs.math.Vector3} v The normal.
 * @return {!o3djs.math.Vector3} The transformed normal.
 */
o3djs.math.matrix4.transformNormal = function(m, v) {
  var mInverse = o3djs.math.inverse4(m);
  var v0 = v[0];
  var v1 = v[1];
  var v2 = v[2];
  var mi0 = mInverse[0];
  var mi1 = mInverse[1];
  var mi2 = mInverse[2];
  var mi3 = mInverse[3];

  return [v0 * mi0[0] + v1 * mi0[1] + v2 * mi0[2],
          v0 * mi1[0] + v1 * mi1[1] + v2 * mi1[2],
          v0 * mi2[0] + v1 * mi2[1] + v2 * mi2[2]];
};

/**
 * Creates a 4-by-4 identity matrix.
 * @return {!o3djs.math.Matrix4} The 4-by-4 identity.
 */
o3djs.math.matrix4.identity = function() {
  return [
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, 1, 0],
    [0, 0, 0, 1]
  ];
};

/**
 * Sets the given 4-by-4 matrix to the identity matrix.
 * @param {!o3djs.math.Matrix4} m The matrix to set to identity.
 * @return {!o3djs.math.Matrix4} m once modified.
 */
o3djs.math.matrix4.setIdentity = function(m) {
  for (var i = 0; i < 4; i++) {
    for (var j = 0; j < 4; j++) {
      if (i == j) {
        m[i][j] = 1;
      } else {
        m[i][j] = 0;
      }
    }
  }
  return m;
};

/**
 * Computes a 4-by-4 perspective transformation matrix given the angular height
 * of the frustum, the aspect ratio, and the near and far clipping planes.  The
 * arguments define a frustum extending in the negative z direction.  The given
 * angle is the vertical angle of the frustum, and the horizontal angle is
 * determined to produce the given aspect ratio.  The arguments near and far are
 * the distances to the near and far clipping planes.  Note that near and far
 * are not z coordinates, but rather they are distances along the negative
 * z-axis.  The matrix generated sends the viewing frustum to the unit box.
 * We assume a unit box extending from -1 to 1 in the x and y dimensions and
 * from 0 to 1 in the z dimension.
 * @param {number} angle The camera angle from top to bottom (in radians).
 * @param {number} aspect The aspect ratio width / height.
 * @param {number} near The depth (negative z coordinate)
 *     of the near clipping plane.
 * @param {number} far The depth (negative z coordinate)
 *     of the far clipping plane.
 * @return {!o3djs.math.Matrix4} The perspective matrix.
 */
o3djs.math.matrix4.perspective = function(angle, aspect, near, far) {
  var f = Math.tan(0.5 * (Math.PI - angle));
  var range = near - far;

  return [
    [f / aspect, 0, 0, 0],
    [0, f, 0, 0],
    [0, 0, far / range, -1],
    [0, 0, near * far / range, 0]
  ];
};

/**
 * Computes a 4-by-4 orthographic projection matrix given the coordinates of the
 * planes defining the axis-aligned, box-shaped viewing volume.  The matrix
 * generated sends that box to the unit box.  Note that although left and right
 * are x coordinates and bottom and top are y coordinates, near and far
 * are not z coordinates, but rather they are distances along the negative
 * z-axis.  We assume a unit box extending from -1 to 1 in the x and y
 * dimensions and from 0 to 1 in the z dimension.
 * @param {number} left The x coordinate of the left plane of the box.
 * @param {number} right The x coordinate of the right plane of the box.
 * @param {number} bottom The y coordinate of the bottom plane of the box.
 * @param {number} top The y coordinate of the right plane of the box.
 * @param {number} near The negative z coordinate of the near plane of the box.
 * @param {number} far The negative z coordinate of the far plane of the box.
 * @return {!o3djs.math.Matrix4} The orthographic projection matrix.
 */
o3djs.math.matrix4.orthographic =
    function(left, right, bottom, top, near, far) {
  return [
    [2 / (right - left), 0, 0, 0],
    [0, 2 / (top - bottom), 0, 0],
    [0, 0, 1 / (near - far), 0],
    [(left + right) / (left - right),
     (bottom + top) / (bottom - top),
     near / (near - far), 1]
  ];
};

/**
 * Computes a 4-by-4 perspective transformation matrix given the left, right,
 * top, bottom, near and far clipping planes. The arguments define a frustum
 * extending in the negative z direction. The arguments near and far are the
 * distances to the near and far clipping planes. Note that near and far are not
 * z coordinates, but rather they are distances along the negative z-axis. The
 * matrix generated sends the viewing frustum to the unit box. We assume a unit
 * box extending from -1 to 1 in the x and y dimensions and from 0 to 1 in the z
 * dimension.
 * @param {number} left The x coordinate of the left plane of the box.
 * @param {number} right The x coordinate of the right plane of the box.
 * @param {number} bottom The y coordinate of the bottom plane of the box.
 * @param {number} top The y coordinate of the right plane of the box.
 * @param {number} near The negative z coordinate of the near plane of the box.
 * @param {number} far The negative z coordinate of the far plane of the box.
 * @return {!o3djs.math.Matrix4} The perspective projection matrix.
 */
o3djs.math.matrix4.frustum = function(left, right, bottom, top, near, far) {
  var dx = (right - left);
  var dy = (top - bottom);
  var dz = (near - far);
  return [
    [2 * near / dx, 0, 0, 0],
    [0, 2 * near / dy, 0, 0],
    [(left + right) / dx, (top + bottom) / dy, far / dz, -1],
    [0, 0, near * far / dz, 0]];
};

/**
 * Computes a 4-by-4 look-at transformation.  The transformation generated is
 * an orthogonal rotation matrix with translation component.  The translation
 * component sends the eye to the origin.  The rotation component sends the
 * vector pointing from the eye to the target to a vector pointing in the
 * negative z direction, and also sends the up vector into the upper half of
 * the yz plane.
 * @param {(!o3djs.math.Vector3|!o3djs.math.Vector4)} eye The position
 *     of the eye.
 * @param {(!o3djs.math.Vector3|!o3djs.math.Vector4)} target The
 *     position meant to be viewed.
 * @param {(!o3djs.math.Vector3|!o3djs.math.Vector4)} up A vector
 *     pointing up.
 * @return {!o3djs.math.Matrix4} The look-at matrix.
 */
o3djs.math.matrix4.lookAt = function(eye, target, up) {
  var vz = o3djs.math.normalize(
      o3djs.math.subVector(eye, target).slice(0, 3)).concat(0);
  var vx = o3djs.math.normalize(
      o3djs.math.cross(up, vz)).concat(0);
  var vy = o3djs.math.cross(vz, vx).concat(0);

  return o3djs.math.inverse([vx, vy, vz, eye.concat(1)]);
};

/**
 * Takes two 4-by-4 matrices, a and b, and computes the product in the order
 * that pre-composes b with a.  In other words, the matrix returned will
 * transform by b first and then a.  Note this is subtly different from just
 * multiplying the matrices together.  For given a and b, this function returns
 * the same object in both row-major and column-major mode.
 * @param {!o3djs.math.Matrix4} a A 4-by-4 matrix.
 * @param {!o3djs.math.Matrix4} b A 4-by-4 matrix.
 * @return {!o3djs.math.Matrix4} the composition of a and b, b first then a.
 */
o3djs.math.matrix4.composition = function(a, b) {
  var a0 = a[0];
  var a1 = a[1];
  var a2 = a[2];
  var a3 = a[3];
  var b0 = b[0];
  var b1 = b[1];
  var b2 = b[2];
  var b3 = b[3];
  var a00 = a0[0];
  var a01 = a0[1];
  var a02 = a0[2];
  var a03 = a0[3];
  var a10 = a1[0];
  var a11 = a1[1];
  var a12 = a1[2];
  var a13 = a1[3];
  var a20 = a2[0];
  var a21 = a2[1];
  var a22 = a2[2];
  var a23 = a2[3];
  var a30 = a3[0];
  var a31 = a3[1];
  var a32 = a3[2];
  var a33 = a3[3];
  var b00 = b0[0];
  var b01 = b0[1];
  var b02 = b0[2];
  var b03 = b0[3];
  var b10 = b1[0];
  var b11 = b1[1];
  var b12 = b1[2];
  var b13 = b1[3];
  var b20 = b2[0];
  var b21 = b2[1];
  var b22 = b2[2];
  var b23 = b2[3];
  var b30 = b3[0];
  var b31 = b3[1];
  var b32 = b3[2];
  var b33 = b3[3];
  return [[a00 * b00 + a10 * b01 + a20 * b02 + a30 * b03,
           a01 * b00 + a11 * b01 + a21 * b02 + a31 * b03,
           a02 * b00 + a12 * b01 + a22 * b02 + a32 * b03,
           a03 * b00 + a13 * b01 + a23 * b02 + a33 * b03],
          [a00 * b10 + a10 * b11 + a20 * b12 + a30 * b13,
           a01 * b10 + a11 * b11 + a21 * b12 + a31 * b13,
           a02 * b10 + a12 * b11 + a22 * b12 + a32 * b13,
           a03 * b10 + a13 * b11 + a23 * b12 + a33 * b13],
          [a00 * b20 + a10 * b21 + a20 * b22 + a30 * b23,
           a01 * b20 + a11 * b21 + a21 * b22 + a31 * b23,
           a02 * b20 + a12 * b21 + a22 * b22 + a32 * b23,
           a03 * b20 + a13 * b21 + a23 * b22 + a33 * b23],
          [a00 * b30 + a10 * b31 + a20 * b32 + a30 * b33,
           a01 * b30 + a11 * b31 + a21 * b32 + a31 * b33,
           a02 * b30 + a12 * b31 + a22 * b32 + a32 * b33,
           a03 * b30 + a13 * b31 + a23 * b32 + a33 * b33]];
};

/**
 * Takes two 4-by-4 matrices, a and b, and modifies a to be the product in the
 * order that pre-composes b with a.  The matrix a, upon modification will
 * transform by b first and then a.  Note this is subtly different from just
 * multiplying the matrices together.  For given a and b, a, upon modification,
 * will be the same object in both row-major and column-major mode.
 * @param {!o3djs.math.Matrix4} a A 4-by-4 matrix.
 * @param {!o3djs.math.Matrix4} b A 4-by-4 matrix.
 * @return {!o3djs.math.Matrix4} a once modified.
 */
o3djs.math.matrix4.compose = function(a, b) {
  var a0 = a[0];
  var a1 = a[1];
  var a2 = a[2];
  var a3 = a[3];
  var b0 = b[0];
  var b1 = b[1];
  var b2 = b[2];
  var b3 = b[3];
  var a00 = a0[0];
  var a01 = a0[1];
  var a02 = a0[2];
  var a03 = a0[3];
  var a10 = a1[0];
  var a11 = a1[1];
  var a12 = a1[2];
  var a13 = a1[3];
  var a20 = a2[0];
  var a21 = a2[1];
  var a22 = a2[2];
  var a23 = a2[3];
  var a30 = a3[0];
  var a31 = a3[1];
  var a32 = a3[2];
  var a33 = a3[3];
  var b00 = b0[0];
  var b01 = b0[1];
  var b02 = b0[2];
  var b03 = b0[3];
  var b10 = b1[0];
  var b11 = b1[1];
  var b12 = b1[2];
  var b13 = b1[3];
  var b20 = b2[0];
  var b21 = b2[1];
  var b22 = b2[2];
  var b23 = b2[3];
  var b30 = b3[0];
  var b31 = b3[1];
  var b32 = b3[2];
  var b33 = b3[3];
  a[0].splice(0, 4, a00 * b00 + a10 * b01 + a20 * b02 + a30 * b03,
                    a01 * b00 + a11 * b01 + a21 * b02 + a31 * b03,
                    a02 * b00 + a12 * b01 + a22 * b02 + a32 * b03,
                    a03 * b00 + a13 * b01 + a23 * b02 + a33 * b03);
  a[1].splice(0, 4, a00 * b10 + a10 * b11 + a20 * b12 + a30 * b13,
                    a01 * b10 + a11 * b11 + a21 * b12 + a31 * b13,
                    a02 * b10 + a12 * b11 + a22 * b12 + a32 * b13,
                    a03 * b10 + a13 * b11 + a23 * b12 + a33 * b13);
  a[2].splice(0, 4, a00 * b20 + a10 * b21 + a20 * b22 + a30 * b23,
                    a01 * b20 + a11 * b21 + a21 * b22 + a31 * b23,
                    a02 * b20 + a12 * b21 + a22 * b22 + a32 * b23,
                    a03 * b20 + a13 * b21 + a23 * b22 + a33 * b23),
  a[3].splice(0, 4, a00 * b30 + a10 * b31 + a20 * b32 + a30 * b33,
                    a01 * b30 + a11 * b31 + a21 * b32 + a31 * b33,
                    a02 * b30 + a12 * b31 + a22 * b32 + a32 * b33,
                    a03 * b30 + a13 * b31 + a23 * b32 + a33 * b33);
  return a;
};

/**
 * Creates a 4-by-4 matrix which translates by the given vector v.
 * @param {(!o3djs.math.Vector3|!o3djs.math.Vector4)} v The vector by
 *     which to translate.
 * @return {!o3djs.math.Matrix4} The translation matrix.
 */
o3djs.math.matrix4.translation = function(v) {
  return [
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, 1, 0],
    [v[0], v[1], v[2], 1]
  ];
};

/**
 * Modifies the given 4-by-4 matrix by translation by the given vector v.
 * @param {!o3djs.math.Matrix4} m The matrix.
 * @param {(!o3djs.math.Vector3|!o3djs.math.Vector4)} v The vector by
 *     which to translate.
 * @return {!o3djs.math.Matrix4} m once modified.
 */
o3djs.math.matrix4.translate = function(m, v) {
  var v0 = v[0];
  var v1 = v[1];
  var v2 = v[2];
  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];
  var m00 = m0[0];
  var m01 = m0[1];
  var m02 = m0[2];
  var m03 = m0[3];
  var m10 = m1[0];
  var m11 = m1[1];
  var m12 = m1[2];
  var m13 = m1[3];
  var m20 = m2[0];
  var m21 = m2[1];
  var m22 = m2[2];
  var m23 = m2[3];
  var m30 = m3[0];
  var m31 = m3[1];
  var m32 = m3[2];
  var m33 = m3[3];

  m3.splice(0, 4, m00 * v0 + m10 * v1 + m20 * v2 + m30,
                  m01 * v0 + m11 * v1 + m21 * v2 + m31,
                  m02 * v0 + m12 * v1 + m22 * v2 + m32,
                  m03 * v0 + m13 * v1 + m23 * v2 + m33);

  return m;
};

/**
 * Creates a 4-by-4 matrix which scales in each dimension by an amount given by
 * the corresponding entry in the given vector; assumes the vector has three
 * entries.
 * @param {!o3djs.math.Vector3} v A vector of
 *     three entries specifying the factor by which to scale in each dimension.
 * @return {!o3djs.math.Matrix4} The scaling matrix.
 */
o3djs.math.matrix4.scaling = function(v) {
  return [
    [v[0], 0, 0, 0],
    [0, v[1], 0, 0],
    [0, 0, v[2], 0],
    [0, 0, 0, 1]
  ];
};

/**
 * Modifies the given 4-by-4 matrix, scaling in each dimension by an amount
 * given by the corresponding entry in the given vector; assumes the vector has
 * three entries.
 * @param {!o3djs.math.Matrix4} m The matrix to be modified.
 * @param {!o3djs.math.Vector3} v A vector of three entries specifying the
 *     factor by which to scale in each dimension.
 * @return {!o3djs.math.Matrix4} m once modified.
 */
o3djs.math.matrix4.scale = function(m, v) {
  var v0 = v[0];
  var v1 = v[1];
  var v2 = v[2];

  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];

  m0.splice(0, 4, v0 * m0[0], v0 * m0[1], v0 * m0[2], v0 * m0[3]);
  m1.splice(0, 4, v1 * m1[0], v1 * m1[1], v1 * m1[2], v1 * m1[3]);
  m2.splice(0, 4, v2 * m2[0], v2 * m2[1], v2 * m2[2], v2 * m2[3]);

  return m;
};

/**
 * Creates a 4-by-4 matrix which rotates around the x-axis by the given angle.
 * @param {number} angle The angle by which to rotate (in radians).
 * @return {!o3djs.math.Matrix4} The rotation matrix.
 */
o3djs.math.matrix4.rotationX = function(angle) {
  var c = Math.cos(angle);
  var s = Math.sin(angle);

  return [
    [1, 0, 0, 0],
    [0, c, s, 0],
    [0, -s, c, 0],
    [0, 0, 0, 1]
  ];
};

/**
 * Modifies the given 4-by-4 matrix by a rotation around the x-axis by the given
 * angle.
 * @param {!o3djs.math.Matrix4} m The matrix.
 * @param {number} angle The angle by which to rotate (in radians).
 * @return {!o3djs.math.Matrix4} m once modified.
 */
o3djs.math.matrix4.rotateX = function(m, angle) {
  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];
  var m10 = m1[0];
  var m11 = m1[1];
  var m12 = m1[2];
  var m13 = m1[3];
  var m20 = m2[0];
  var m21 = m2[1];
  var m22 = m2[2];
  var m23 = m2[3];
  var c = Math.cos(angle);
  var s = Math.sin(angle);

  m1.splice(0, 4, c * m10 + s * m20,
                  c * m11 + s * m21,
                  c * m12 + s * m22,
                  c * m13 + s * m23);
  m2.splice(0, 4, c * m20 - s * m10,
                  c * m21 - s * m11,
                  c * m22 - s * m12,
                  c * m23 - s * m13);

  return m;
};

/**
 * Creates a 4-by-4 matrix which rotates around the y-axis by the given angle.
 * @param {number} angle The angle by which to rotate (in radians).
 * @return {!o3djs.math.Matrix4} The rotation matrix.
 */
o3djs.math.matrix4.rotationY = function(angle) {
  var c = Math.cos(angle);
  var s = Math.sin(angle);

  return [
    [c, 0, -s, 0],
    [0, 1, 0, 0],
    [s, 0, c, 0],
    [0, 0, 0, 1]
  ];
};

/**
 * Modifies the given 4-by-4 matrix by a rotation around the y-axis by the given
 * angle.
 * @param {!o3djs.math.Matrix4} m The matrix.
 * @param {number} angle The angle by which to rotate (in radians).
 * @return {!o3djs.math.Matrix4} m once modified.
 */
o3djs.math.matrix4.rotateY = function(m, angle) {
  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];
  var m00 = m0[0];
  var m01 = m0[1];
  var m02 = m0[2];
  var m03 = m0[3];
  var m20 = m2[0];
  var m21 = m2[1];
  var m22 = m2[2];
  var m23 = m2[3];
  var c = Math.cos(angle);
  var s = Math.sin(angle);

  m0.splice(0, 4, c * m00 - s * m20,
                  c * m01 - s * m21,
                  c * m02 - s * m22,
                  c * m03 - s * m23);
  m2.splice(0, 4, c * m20 + s * m00,
                  c * m21 + s * m01,
                  c * m22 + s * m02,
                  c * m23 + s * m03);

  return m;
};

/**
 * Creates a 4-by-4 matrix which rotates around the z-axis by the given angle.
 * @param {number} angle The angle by which to rotate (in radians).
 * @return {!o3djs.math.Matrix4} The rotation matrix.
 */
o3djs.math.matrix4.rotationZ = function(angle) {
  var c = Math.cos(angle);
  var s = Math.sin(angle);

  return [
    [c, s, 0, 0],
    [-s, c, 0, 0],
    [0, 0, 1, 0],
    [0, 0, 0, 1]
  ];
};

/**
 * Modifies the given 4-by-4 matrix by a rotation around the z-axis by the given
 * angle.
 * @param {!o3djs.math.Matrix4} m The matrix.
 * @param {number} angle The angle by which to rotate (in radians).
 * @return {!o3djs.math.Matrix4} m once modified.
 */
o3djs.math.matrix4.rotateZ = function(m, angle) {
  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];
  var m00 = m0[0];
  var m01 = m0[1];
  var m02 = m0[2];
  var m03 = m0[3];
  var m10 = m1[0];
  var m11 = m1[1];
  var m12 = m1[2];
  var m13 = m1[3];
  var c = Math.cos(angle);
  var s = Math.sin(angle);

  m0.splice(0, 4, c * m00 + s * m10,
                  c * m01 + s * m11,
                  c * m02 + s * m12,
                  c * m03 + s * m13);
  m1.splice(0, 4, c * m10 - s * m00,
                  c * m11 - s * m01,
                  c * m12 - s * m02,
                  c * m13 - s * m03);

  return m;
};

/**
 * Creates a 4-by-4 rotation matrix.  Interprets the entries of the given
 * vector as angles by which to rotate around the x, y and z axes, returns a
 * a matrix which rotates around the x-axis first, then the y-axis, then the
 * z-axis.
 * @param {!o3djs.math.Vector3} v A vector of angles (in radians).
 * @return {!o3djs.math.Matrix4} The rotation matrix.
 */
o3djs.math.matrix4.rotationZYX = function(v) {
  var sinx = Math.sin(v[0]);
  var cosx = Math.cos(v[0]);
  var siny = Math.sin(v[1]);
  var cosy = Math.cos(v[1]);
  var sinz = Math.sin(v[2]);
  var cosz = Math.cos(v[2]);

  var coszsiny = cosz * siny;
  var sinzsiny = sinz * siny;

  return [
    [cosz * cosy, sinz * cosy, -siny, 0],
    [coszsiny * sinx - sinz * cosx,
     sinzsiny * sinx + cosz * cosx,
     cosy * sinx,
     0],
    [coszsiny * cosx + sinz * sinx,
     sinzsiny * cosx - cosz * sinx,
     cosy * cosx,
     0],
    [0, 0, 0, 1]
  ];
};

/**
 * Modifies a 4-by-4 matrix by a rotation.  Interprets the coordinates of the
 * given vector as angles by which to rotate around the x, y and z axes, rotates
 * around the x-axis first, then the y-axis, then the z-axis.
 * @param {!o3djs.math.Matrix4} m The matrix.
 * @param {!o3djs.math.Vector3} v A vector of angles (in radians).
 * @return {!o3djs.math.Matrix4} m once modified.
 */
o3djs.math.matrix4.rotateZYX = function(m, v) {
  var sinX = Math.sin(v[0]);
  var cosX = Math.cos(v[0]);
  var sinY = Math.sin(v[1]);
  var cosY = Math.cos(v[1]);
  var sinZ = Math.sin(v[2]);
  var cosZ = Math.cos(v[2]);

  var cosZSinY = cosZ * sinY;
  var sinZSinY = sinZ * sinY;

  var r00 = cosZ * cosY;
  var r01 = sinZ * cosY;
  var r02 = -sinY;
  var r10 = cosZSinY * sinX - sinZ * cosX;
  var r11 = sinZSinY * sinX + cosZ * cosX;
  var r12 = cosY * sinX;
  var r20 = cosZSinY * cosX + sinZ * sinX;
  var r21 = sinZSinY * cosX - cosZ * sinX;
  var r22 = cosY * cosX;

  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];

  var m00 = m0[0];
  var m01 = m0[1];
  var m02 = m0[2];
  var m03 = m0[3];
  var m10 = m1[0];
  var m11 = m1[1];
  var m12 = m1[2];
  var m13 = m1[3];
  var m20 = m2[0];
  var m21 = m2[1];
  var m22 = m2[2];
  var m23 = m2[3];
  var m30 = m3[0];
  var m31 = m3[1];
  var m32 = m3[2];
  var m33 = m3[3];

  m0.splice(0, 4,
      r00 * m00 + r01 * m10 + r02 * m20,
      r00 * m01 + r01 * m11 + r02 * m21,
      r00 * m02 + r01 * m12 + r02 * m22,
      r00 * m03 + r01 * m13 + r02 * m23);

  m1.splice(0, 4,
      r10 * m00 + r11 * m10 + r12 * m20,
      r10 * m01 + r11 * m11 + r12 * m21,
      r10 * m02 + r11 * m12 + r12 * m22,
      r10 * m03 + r11 * m13 + r12 * m23);

  m2.splice(0, 4,
      r20 * m00 + r21 * m10 + r22 * m20,
      r20 * m01 + r21 * m11 + r22 * m21,
      r20 * m02 + r21 * m12 + r22 * m22,
      r20 * m03 + r21 * m13 + r22 * m23);

  return m;
};

/**
 * Creates a 4-by-4 matrix which rotates around the given axis by the given
 * angle.
 * @param {(!o3djs.math.Vector3|!o3djs.math.Vector4)} axis The axis
 *     about which to rotate.
 * @param {number} angle The angle by which to rotate (in radians).
 * @return {!o3djs.math.Matrix4} A matrix which rotates angle radians
 *     around the axis.
 */
o3djs.math.matrix4.axisRotation = function(axis, angle) {
  var x = axis[0];
  var y = axis[1];
  var z = axis[2];
  var n = Math.sqrt(x * x + y * y + z * z);
  x /= n;
  y /= n;
  z /= n;
  var xx = x * x;
  var yy = y * y;
  var zz = z * z;
  var c = Math.cos(angle);
  var s = Math.sin(angle);
  var oneMinusCosine = 1 - c;

  return [
    [xx + (1 - xx) * c,
     x * y * oneMinusCosine + z * s,
     x * z * oneMinusCosine - y * s,
     0],
    [x * y * oneMinusCosine - z * s,
     yy + (1 - yy) * c,
     y * z * oneMinusCosine + x * s,
     0],
    [x * z * oneMinusCosine + y * s,
     y * z * oneMinusCosine - x * s,
     zz + (1 - zz) * c,
     0],
    [0, 0, 0, 1]
  ];
};

/**
 * Modifies the given 4-by-4 matrix by rotation around the given axis by the
 * given angle.
 * @param {!o3djs.math.Matrix4} m The matrix.
 * @param {(!o3djs.math.Vector3|!o3djs.math.Vector4)} axis The axis
 *     about which to rotate.
 * @param {number} angle The angle by which to rotate (in radians).
 * @return {!o3djs.math.Matrix4} m once modified.
 */
o3djs.math.matrix4.axisRotate = function(m, axis, angle) {
  var x = axis[0];
  var y = axis[1];
  var z = axis[2];
  var n = Math.sqrt(x * x + y * y + z * z);
  x /= n;
  y /= n;
  z /= n;
  var xx = x * x;
  var yy = y * y;
  var zz = z * z;
  var c = Math.cos(angle);
  var s = Math.sin(angle);
  var oneMinusCosine = 1 - c;

  var r00 = xx + (1 - xx) * c;
  var r01 = x * y * oneMinusCosine + z * s;
  var r02 = x * z * oneMinusCosine - y * s;
  var r10 = x * y * oneMinusCosine - z * s;
  var r11 = yy + (1 - yy) * c;
  var r12 = y * z * oneMinusCosine + x * s;
  var r20 = x * z * oneMinusCosine + y * s;
  var r21 = y * z * oneMinusCosine - x * s;
  var r22 = zz + (1 - zz) * c;

  var m0 = m[0];
  var m1 = m[1];
  var m2 = m[2];
  var m3 = m[3];

  var m00 = m0[0];
  var m01 = m0[1];
  var m02 = m0[2];
  var m03 = m0[3];
  var m10 = m1[0];
  var m11 = m1[1];
  var m12 = m1[2];
  var m13 = m1[3];
  var m20 = m2[0];
  var m21 = m2[1];
  var m22 = m2[2];
  var m23 = m2[3];
  var m30 = m3[0];
  var m31 = m3[1];
  var m32 = m3[2];
  var m33 = m3[3];

  m0.splice(0, 4,
      r00 * m00 + r01 * m10 + r02 * m20,
      r00 * m01 + r01 * m11 + r02 * m21,
      r00 * m02 + r01 * m12 + r02 * m22,
      r00 * m03 + r01 * m13 + r02 * m23);

  m1.splice(0, 4,
      r10 * m00 + r11 * m10 + r12 * m20,
      r10 * m01 + r11 * m11 + r12 * m21,
      r10 * m02 + r11 * m12 + r12 * m22,
      r10 * m03 + r11 * m13 + r12 * m23);

  m2.splice(0, 4,
      r20 * m00 + r21 * m10 + r22 * m20,
      r20 * m01 + r21 * m11 + r22 * m21,
      r20 * m02 + r21 * m12 + r22 * m22,
      r20 * m03 + r21 * m13 + r22 * m23);

  return m;
};

/**
 * Sets each function in the namespace o3djs.math to the row major
 * version in o3djs.math.rowMajor (provided such a function exists in
 * o3djs.math.rowMajor).  Call this function to establish the row major
 * convention.
 */
o3djs.math.installRowMajorFunctions = function() {
  for (var f in o3djs.math.rowMajor) {
    o3djs.math[f] = o3djs.math.rowMajor[f];
  }
};

/**
 * Sets each function in the namespace o3djs.math to the column major
 * version in o3djs.math.columnMajor (provided such a function exists in
 * o3djs.math.columnMajor).  Call this function to establish the column
 * major convention.
 */
o3djs.math.installColumnMajorFunctions = function() {
  for (var f in o3djs.math.columnMajor) {
    o3djs.math[f] = o3djs.math.columnMajor[f];
  }
};

/**
 * Sets each function in the namespace o3djs.math to the error checking
 * version in o3djs.math.errorCheck (provided such a function exists in
 * o3djs.math.errorCheck).
 */
o3djs.math.installErrorCheckFunctions = function() {
  for (var f in o3djs.math.errorCheck) {
    o3djs.math[f] = o3djs.math.errorCheck[f];
  }
};

/**
 * Sets each function in the namespace o3djs.math to the error checking free
 * version in o3djs.math.errorCheckFree (provided such a function exists in
 * o3djs.math.errorCheckFree).
 */
o3djs.math.installErrorCheckFreeFunctions = function() {
  for (var f in o3djs.math.errorCheckFree) {
    o3djs.math[f] = o3djs.math.errorCheckFree[f];
  }
}

// By default, install the row-major functions.
o3djs.math.installRowMajorFunctions();

// By default, install prechecking.
o3djs.math.installErrorCheckFunctions();
/*
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview This file contains various functions for helping setup
 * packs for o3d.  It puts them in the "pack" module on the o3djs
 * object.
 *
 *     Note: This library is only a sample. It is not meant to be some official
 *     library. It is provided only as example code.
 *
 */

o3djs.provide('o3djs.pack');

/**
 * A Module with utilities for dealing with packs..
 * @namespace
 */
o3djs.pack = o3djs.pack || {};

/**
 * Prepares a pack for rendering.
 *
 * @param {!o3d.Pack} pack Pack to prepare.
 * @param {!o3djs.rendergraph.ViewInfo} viewInfo as returned from
 *     o3djs.rendergraph.createView.
 * @param {!o3d.Pack} opt_effectPack Pack to create effects in. If this is
 *     not specifed the pack to prepare above will be used.
 *
 * @see o3djs.material.prepareMaterials
 * @see o3djs.shape.prepareShapes
 */
o3djs.pack.preparePack = function(pack, viewInfo, opt_effectPack) {
  o3djs.material.prepareMaterials(pack, viewInfo, opt_effectPack);
  o3djs.shape.prepareShapes(pack);
};

/*
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview This file contains various functions and classes for rendering
 * gpu based particles.
 *
 * TODO: Add 3d oriented particles.
 */

o3djs.provide('o3djs.particles');

/**
 * A Module with various GPU particle functions and classes.
 * Note: GPU particles have the issue that they are not sorted per particle
 * but rather per emitter.
 * @namespace
 */
o3djs.particles = o3djs.particles || {};

/**
 * Enum for pre-made particle states.
 * @enum
 */
o3djs.particles.ParticleStateIds = {
   BLEND: 0,
   ADD: 1,
   BLEND_PREMULTIPLY: 2,
   BLEND_NO_ALPHA: 3,
   SUBTRACT: 4,
   INVERSE: 5};

/**
 * Particle Effect strings
 * @type {!Array.<{name: string, fxString: string}>}
 */
o3djs.particles.FX_STRINGS_CG = [
  { name: 'particle3d', fxString: '' +
    'float4x4 worldViewProjection : WORLDVIEWPROJECTION;\n' +
    'float4x4 world : WORLD;\n' +
    'float3 worldVelocity;\n' +
    'float3 worldAcceleration;\n' +
    'float timeRange;\n' +
    'float time;\n' +
    'float timeOffset;\n' +
    'float frameDuration;\n' +
    'float numFrames;\n' +
    '\n' +
    '// We need to implement 1D!\n' +
    'sampler rampSampler;\n' +
    'sampler colorSampler;\n' +
    '\n' +
    'struct VertexShaderInput {\n' +
    '  float4 uvLifeTimeFrameStart : POSITION; // uv, lifeTime, frameStart\n' +
    '  float4 positionStartTime : TEXCOORD0;    // position.xyz, startTime\n' +
    '  float4 velocityStartSize : TEXCOORD1;   // velocity.xyz, startSize\n' +
    '  float4 accelerationEndSize : TEXCOORD2; // acceleration.xyz, endSize\n' +
    '  float4 spinStartSpinSpeed : TEXCOORD3;  // spinStart.x, spinSpeed.y\n' +
    '  float4 orientation : TEXCOORD4;  // orientation\n' +
    '  float4 colorMult : COLOR; //\n' +
    '};\n' +
    '\n' +
    'struct PixelShaderInput {\n' +
    '  float4 position : POSITION;\n' +
    '  float2 texcoord : TEXCOORD0;\n' +
    '  float1 percentLife : TEXCOORD1;\n' +
    '  float4 colorMult: TEXCOORD2;\n' +
    '};\n' +
    '\n' +
    'PixelShaderInput vertexShaderFunction(VertexShaderInput input) {\n' +
    '  PixelShaderInput output;\n' +
    '\n' +
    '  float2 uv = input.uvLifeTimeFrameStart.xy;\n' +
    '  float lifeTime = input.uvLifeTimeFrameStart.z;\n' +
    '  float frameStart = input.uvLifeTimeFrameStart.w;\n' +
    '  float3 position = input.positionStartTime.xyz;\n' +
    '  float startTime = input.positionStartTime.w;\n' +
    '  float3 velocity = mul(float4(input.velocityStartSize.xyz, 0),\n' +
    '                        world).xyz + worldVelocity;\n' +
    '  float startSize = input.velocityStartSize.w;\n' +
    '  float3 acceleration = mul(float4(input.accelerationEndSize.xyz, 0),\n' +
    '                            world).xyz + worldAcceleration;\n' +
    '  float endSize = input.accelerationEndSize.w;\n' +
    '  float spinStart = input.spinStartSpinSpeed.x;\n' +
    '  float spinSpeed = input.spinStartSpinSpeed.y;\n' +
    '\n' +
    '  float localTime = fmod((time - timeOffset - startTime), timeRange);\n' +
    '  float percentLife = localTime / lifeTime;\n' +
    '\n' +
    '  float frame = fmod(floor(localTime / frameDuration + frameStart),\n' +
    '                     numFrames);\n' +
    '  float uOffset = frame / numFrames;\n' +
    '  float u = uOffset + (uv.x + 0.5) * (1 / numFrames);\n' +
    '\n' +
    '  output.texcoord = float2(u, uv.y + 0.5);\n' +
    '  output.colorMult = input.colorMult;\n' +
    '\n' +
    '  float size = lerp(startSize, endSize, percentLife);\n' +
    '  size = (percentLife < 0 || percentLife > 1) ? 0 : size;\n' +
    '  float s = sin(spinStart + spinSpeed * localTime);\n' +
    '  float c = cos(spinStart + spinSpeed * localTime);\n' +
    '\n' +
    '  float4 rotatedPoint = float4((uv.x * c + uv.y * s) * size, 0,\n' +
    '                               (uv.x * s - uv.y * c) * size, 1);\n' +
    '  float3 center = velocity * localTime +\n' +
    '                  acceleration * localTime * localTime + \n' +
    '                  position;\n' +
    '  \n' +
    '      float4 q2 = input.orientation + input.orientation;\n' +
    '      float4 qx = input.orientation.xxxw * q2.xyzx;\n' +
    '      float4 qy = input.orientation.xyyw * q2.xyzy;\n' +
    '      float4 qz = input.orientation.xxzw * q2.xxzz;\n' +
    '  \n' +
    '      float4x4 localMatrix = float4x4(\n' +
    '        (1.0f - qy.y) - qz.z, \n' +
    '        qx.y + qz.w, \n' +
    '        qx.z - qy.w,\n' +
    '        0,\n' +
    '  \n' +
    '        qx.y - qz.w, \n' +
    '        (1.0f - qx.x) - qz.z, \n' +
    '        qy.z + qx.w,\n' +
    '        0,\n' +
    '  \n' +
    '        qx.z + qy.w, \n' +
    '        qy.z - qx.w, \n' +
    '        (1.0f - qx.x) - qy.y,\n' +
    '        0,\n' +
    '  \n' +
    '        center.x, center.y, center.z, 1);\n' +
    '  rotatedPoint = mul(rotatedPoint, localMatrix);\n' +
    '  output.position = mul(rotatedPoint, worldViewProjection);\n' +
    '  output.percentLife = percentLife;\n' +
    '  return output;\n' +
    '}\n' +
    '\n' +
    'float4 pixelShaderFunction(PixelShaderInput input): COLOR {\n' +
    '  float4 colorMult = tex2D(rampSampler, \n' +
    '                           float2(input.percentLife, 0.5)) *\n' +
    '                     input.colorMult;\n' +
    '  float4 color = tex2D(colorSampler, input.texcoord) * colorMult;\n' +
    '  return color;\n' +
    '}\n' +
    '\n' +
    '// #o3d VertexShaderEntryPoint vertexShaderFunction\n' +
    '// #o3d PixelShaderEntryPoint pixelShaderFunction\n' +
    '// #o3d MatrixLoadOrder RowMajor\n'},
  { name: 'particle2d', fxString: '' +
    'float4x4 viewProjection : VIEWPROJECTION;\n' +
    'float4x4 world : WORLD;\n' +
    'float4x4 viewInverse : VIEWINVERSE;\n' +
    'float3 worldVelocity;\n' +
    'float3 worldAcceleration;\n' +
    'float timeRange;\n' +
    'float time;\n' +
    'float timeOffset;\n' +
    'float frameDuration;\n' +
    'float numFrames;\n' +
    '\n' +
    '// We need to implement 1D!\n' +
    'sampler rampSampler;\n' +
    'sampler colorSampler;\n' +
    '\n' +
    'struct VertexShaderInput {\n' +
    '  float4 uvLifeTimeFrameStart : POSITION; // uv, lifeTime, frameStart\n' +
    '  float4 positionStartTime : TEXCOORD0;    // position.xyz, startTime\n' +
    '  float4 velocityStartSize : TEXCOORD1;   // velocity.xyz, startSize\n' +
    '  float4 accelerationEndSize : TEXCOORD2; // acceleration.xyz, endSize\n' +
    '  float4 spinStartSpinSpeed : TEXCOORD3;  // spinStart.x, spinSpeed.y\n' +
    '  float4 colorMult : COLOR; //\n' +
    '};\n' +
    '\n' +
    'struct PixelShaderInput {\n' +
    '  float4 position : POSITION;\n' +
    '  float2 texcoord : TEXCOORD0;\n' +
    '  float1 percentLife : TEXCOORD1;\n' +
    '  float4 colorMult: TEXCOORD2;\n' +
    '};\n' +
    '\n' +
    'PixelShaderInput vertexShaderFunction(VertexShaderInput input) {\n' +
    '  PixelShaderInput output;\n' +
    '\n' +
    '  float2 uv = input.uvLifeTimeFrameStart.xy;\n' +
    '  float lifeTime = input.uvLifeTimeFrameStart.z;\n' +
    '  float frameStart = input.uvLifeTimeFrameStart.w;\n' +
    '  float3 position = mul(float4(input.positionStartTime.xyz, 1),\n' +
    '                        world).xyz;\n' +
    '  float startTime = input.positionStartTime.w;\n' +
    '  float3 velocity = mul(float4(input.velocityStartSize.xyz, 0),\n' +
    '                        world).xyz + worldVelocity;\n' +
    '  float startSize = input.velocityStartSize.w;\n' +
    '  float3 acceleration = mul(float4(input.accelerationEndSize.xyz, 0),\n' +
    '                            world).xyz + worldAcceleration;\n' +
    '  float endSize = input.accelerationEndSize.w;\n' +
    '  float spinStart = input.spinStartSpinSpeed.x;\n' +
    '  float spinSpeed = input.spinStartSpinSpeed.y;\n' +
    '\n' +
    '  float localTime = fmod((time - timeOffset - startTime), timeRange);\n' +
    '  float percentLife = localTime / lifeTime;\n' +
    '\n' +
    '  float frame = fmod(floor(localTime / frameDuration + frameStart),\n' +
    '                     numFrames);\n' +
    '  float uOffset = frame / numFrames;\n' +
    '  float u = uOffset + (uv.x + 0.5) * (1 / numFrames);\n' +
    '\n' +
    '  output.texcoord = float2(u, uv.y + 0.5);\n' +
    '  output.colorMult = input.colorMult;\n' +
    '\n' +
    '  float3 basisX = viewInverse[0].xyz;\n' +
    '  float3 basisZ = viewInverse[1].xyz;\n' +
    '\n' +
    '  float size = lerp(startSize, endSize, percentLife);\n' +
    '  size = (percentLife < 0 || percentLife > 1) ? 0 : size;\n' +
    '  float s = sin(spinStart + spinSpeed * localTime);\n' +
    '  float c = cos(spinStart + spinSpeed * localTime);\n' +
    '\n' +
    '  float2 rotatedPoint = float2(uv.x * c + uv.y * s, \n' +
    '                               -uv.x * s + uv.y * c);\n' +
    '  float3 localPosition = float3(basisX * rotatedPoint.x +\n' +
    '                                basisZ * rotatedPoint.y) * size +\n' +
    '                         velocity * localTime +\n' +
    '                         acceleration * localTime * localTime + \n' +
    '                         position;\n' +
    '\n' +
    '  output.position = mul(float4(localPosition, 1), \n' +
    '                        viewProjection);\n' +
    '  output.percentLife = percentLife;\n' +
    '  return output;\n' +
    '}\n' +
    '\n' +
    'float4 pixelShaderFunction(PixelShaderInput input): COLOR {\n' +
    '  float4 colorMult = tex2D(rampSampler, \n' +
    '                           float2(input.percentLife, 0.5)) *\n' +
    '                     input.colorMult;\n' +
    '  float4 color = tex2D(colorSampler, input.texcoord) * colorMult;\n' +
    '  return color;\n' +
    '}\n' +
    '\n' +
    '// #o3d VertexShaderEntryPoint vertexShaderFunction\n' +
    '// #o3d PixelShaderEntryPoint pixelShaderFunction\n' +
    '// #o3d MatrixLoadOrder RowMajor\n'}];

o3djs.particles.FX_STRINGS_GLSL = [
  { name: 'particle3d', fxString: '' +
    'uniform mat4 world;\n' +
    'uniform mat4 worldViewProjection;\n' +
    'uniform vec3 worldVelocity;\n' +
    'uniform vec3 worldAcceleration;\n' +
    'uniform float timeRange;\n' +
    'uniform float time;\n' +
    'uniform float timeOffset;\n' +
    'uniform float frameDuration;\n' +
    'uniform float numFrames;\n' +
    '\n' +
    'attribute vec4 position; // uv, lifeTime, frameStart\n' +
    'attribute vec4 texCoord0; // position.xyz, startTime\n' +
    'attribute vec4 texCoord1; // velocity.xyz, startSize\n' +
    'attribute vec4 texCoord2; // acceleration.xyz, endSize\n' +
    'attribute vec4 texCoord3; // spinStart.x, spinSpeed.y\n' +
    'attribute vec4 texCoord4; // orientation\n' +
    'attribute vec4 color; //\n' +
    '\n' +
    'varying vec4 v_position;\n' +
    'varying vec2 v_texcoord;\n' +
    'varying float v_percentLife;\n' +
    'varying vec4 v_colorMult;\n' +
    '\n' +
    'void main() {\n' +
    '  vec4 uvLifeTimeFrameStart = position;\n' +
    '  vec4 positionStartTime = texCoord0;\n' +
    '  vec4 velocityStartSize = texCoord1;\n' +
    '  vec4 accelerationEndSize = texCoord2;\n' +
    '  vec4 spinStartSpinSpeed = texCoord3;\n' +
    '  vec4 orientation = texCoord4;\n' +
    '  vec4 colorMult = color;\n' +
    '  vec2 uv = uvLifeTimeFrameStart.xy;\n' +
    '  float lifeTime = uvLifeTimeFrameStart.z;\n' +
    '  float frameStart = uvLifeTimeFrameStart.w;\n' +
    '  vec3 position = positionStartTime.xyz;\n' +
    '  float startTime = positionStartTime.w;\n' +
    '  vec3 velocity = (world * vec4(velocityStartSize.xyz, 0)).xyz\n' +
    '      + worldVelocity;\n' +
    '  float startSize = velocityStartSize.w;\n' +
    '  vec3 acceleration = (world *\n' +
    '      vec4(accelerationEndSize.xyz, 0)).xyz + worldAcceleration;\n' +
    '  float endSize = accelerationEndSize.w;\n' +
    '  float spinStart = spinStartSpinSpeed.x;\n' +
    '  float spinSpeed = spinStartSpinSpeed.y;\n' +
    '\n' +
    '  float localTime = mod((time - timeOffset - startTime),\n' +
    '      timeRange);\n' +
    '  float percentLife = localTime / lifeTime;\n' +
    '\n' +
    '  float frame = mod(floor(localTime / frameDuration + frameStart),\n' +
    '                     numFrames);\n' +
    '  float uOffset = frame / numFrames;\n' +
    '  float u = uOffset + (uv.x + 0.5) * (1.0 / numFrames);\n' +
    '\n' +
    '  v_texcoord = vec2(u, uv.y + 0.5);\n' +
    '  v_colorMult = colorMult;\n' +
    '\n' +
    '  float size = mix(startSize, endSize, percentLife);\n' +
    '  size = (percentLife < 0.0 || percentLife > 1.0) ? 0.0 : size;\n' +
    '  float s = sin(spinStart + spinSpeed * localTime);\n' +
    '  float c = cos(spinStart + spinSpeed * localTime);\n' +
    '\n' +
    '  vec4 rotatedPoint = vec4((uv.x * c + uv.y * s) * size, 0.0,\n' +
    '                               (uv.x * s - uv.y * c) * size, 1.0);\n' +
    '  vec3 center = velocity * localTime +\n' +
    '                  acceleration * localTime * localTime + \n' +
    '                  position;\n' +
    '  \n' +
    '      vec4 q2 = orientation + orientation;\n' +
    '      vec4 qx = orientation.xxxw * q2.xyzx;\n' +
    '      vec4 qy = orientation.xyyw * q2.xyzy;\n' +
    '      vec4 qz = orientation.xxzw * q2.xxzz;\n' +
    '  \n' +
    '      mat4 localMatrix = mat4(\n' +
    '        (1.0 - qy.y) - qz.z, \n' +
    '        qx.y + qz.w, \n' +
    '        qx.z - qy.w,\n' +
    '        0,\n' +
    '  \n' +
    '        qx.y - qz.w, \n' +
    '        (1.0 - qx.x) - qz.z, \n' +
    '        qy.z + qx.w,\n' +
    '        0,\n' +
    '  \n' +
    '        qx.z + qy.w, \n' +
    '        qy.z - qx.w, \n' +
    '        (1.0 - qx.x) - qy.y,\n' +
    '        0,\n' +
    '  \n' +
    '        center.x, center.y, center.z, 1.0);\n' +
    '  rotatedPoint = localMatrix * rotatedPoint;\n' +
    '  gl_Position = worldViewProjection * rotatedPoint;\n' +
    '  v_percentLife = percentLife;\n' +
    '}\n' +
    '\n' +
    '// #o3d SplitMarker\n' +
    '\n' +
    'varying vec4 v_position;\n' +
    'varying vec2 v_texcoord;\n' +
    'varying float v_percentLife;\n' +
    'varying vec4 v_colorMult;\n' +
    '\n' +
    '// We need to implement 1D!\n' +
    'uniform sampler2D rampSampler;\n' +
    'uniform sampler2D colorSampler;\n' +
    '\n' +
    'void main() {\n' +
    '  vec4 colorMult = texture2D(rampSampler, \n' +
    '      vec2(v_percentLife, 0.5)) * v_colorMult;\n' +
    '  vec4 color = texture2D(colorSampler, v_texcoord) * colorMult;\n' +
    '  gl_FragColor = color;\n' +
    '}\n' +
    '\n' +
    '// #o3d MatrixLoadOrder RowMajor\n'},
  { name: 'particle2d', fxString: '' +
    'uniform mat4 viewProjection;\n' +
    'uniform mat4 world;\n' +
    'uniform mat4 viewInverse;\n' +
    'uniform vec3 worldVelocity;\n' +
    'uniform vec3 worldAcceleration;\n' +
    'uniform float timeRange;\n' +
    'uniform float time;\n' +
    'uniform float timeOffset;\n' +
    'uniform float frameDuration;\n' +
    'uniform float numFrames;\n' +
    '\n' +
    'attribute vec4 position; // uv, lifeTime, frameStart\n' +
    'attribute vec4 texCoord0; // position.xyz, startTime\n' +
    'attribute vec4 texCoord1; // velocity.xyz, startSize\n' +
    'attribute vec4 texCoord2; // acceleration.xyz, endSize\n' +
    'attribute vec4 texCoord3; // spinStart.x, spinSpeed.y\n' +
    'attribute vec4 color; //\n' +
    '\n' +
    'varying vec4 v_position;\n' +
    'varying vec2 v_texcoord;\n' +
    'varying float v_percentLife;\n' +
    'varying vec4 v_colorMult;\n' +
    '\n' +
    'void main() {\n' +
    '  vec4 uvLifeTimeFrameStart = position;\n' +
    '  vec4 positionStartTime = texCoord0;\n' +
    '  vec4 velocityStartSize = texCoord1;\n' +
    '  vec4 accelerationEndSize = texCoord2;\n' +
    '  vec4 spinStartSpinSpeed = texCoord3;\n' +
    '  vec4 colorMult = color;\n' +
    '  vec2 uv = uvLifeTimeFrameStart.xy;\n' +
    '  float lifeTime = uvLifeTimeFrameStart.z;\n' +
    '  float frameStart = uvLifeTimeFrameStart.w;\n' +
    '  vec3 position = (world * vec4(positionStartTime.xyz, 1.0)).xyz;\n' +
    '  float startTime = positionStartTime.w;\n' +
    '  vec3 velocity = (world * vec4(velocityStartSize.xyz, 0)).xyz \n' +
    '      + worldVelocity;\n' +
    '  float startSize = velocityStartSize.w;\n' +
    '  vec3 acceleration = (world *\n' +
    '      vec4(accelerationEndSize.xyz, 0)).xyz + worldAcceleration;\n' +
    '  float endSize = accelerationEndSize.w;\n' +
    '  float spinStart = spinStartSpinSpeed.x;\n' +
    '  float spinSpeed = spinStartSpinSpeed.y;\n' +
    '\n' +
    '  float localTime = mod((time - timeOffset - startTime),\n' +
    '      timeRange);\n' +
    '  float percentLife = localTime / lifeTime;\n' +
    '\n' +
    '  float frame = mod(floor(localTime / frameDuration + frameStart),\n' +
    '                     numFrames);\n' +
    '  float uOffset = frame / numFrames;\n' +
    '  float u = uOffset + (uv.x + 0.5) * (1.0 / numFrames);\n' +
    '\n' +
    '  v_texcoord = vec2(u, uv.y + 0.5);\n' +
    '  v_colorMult = colorMult;\n' +
    '\n' +
    '  vec3 basisX = viewInverse[0].xyz;\n' +
    '  vec3 basisZ = viewInverse[1].xyz;\n' +
    '\n' +
    '  float size = mix(startSize, endSize, percentLife);\n' +
    '  size = (percentLife < 0.0 || percentLife > 1.0) ? 0.0 : size;\n' +
    '  float s = sin(spinStart + spinSpeed * localTime);\n' +
    '  float c = cos(spinStart + spinSpeed * localTime);\n' +
    '\n' +
    '  vec2 rotatedPoint = vec2(uv.x * c + uv.y * s, \n' +
    '                               -uv.x * s + uv.y * c);\n' +
    '  vec3 localPosition = vec3(basisX * rotatedPoint.x +\n' +
    '                                basisZ * rotatedPoint.y) * size +\n' +
    '                         velocity * localTime +\n' +
    '                         acceleration * localTime * localTime + \n' +
    '                         position;\n' +
    '\n' +
    '  gl_Position = (viewProjection * vec4(localPosition, 1.0));\n' +
    '  v_percentLife = percentLife;\n' +
    '}\n' +
    '\n' +
    '// #o3d SplitMarker\n' +
    '\n' +
    'varying vec4 v_position;\n' +
    'varying vec2 v_texcoord;\n' +
    'varying float v_percentLife;\n' +
    'varying vec4 v_colorMult;\n' +
    '\n' +
    '// We need to implement 1D!\n' +
    'uniform sampler2D rampSampler;\n' +
    'uniform sampler2D colorSampler;\n' +
    '\n' +
    'void main() {\n' +
    '  vec4 colorMult = texture2D(rampSampler, \n' +
    '      vec2(v_percentLife, 0.5)) * v_colorMult;\n' +
    '  vec4 color = texture2D(colorSampler, v_texcoord) * colorMult;\n' +
    '  gl_FragColor = color;\n' +
    '}\n' +
    '\n' +
    '// #o3d MatrixLoadOrder RowMajor\n'}];


/**
 * Sets the current shaders language to be in accordance with effect.js.
 */
o3djs.particles.useCorrectShaders_ = function() {
  o3djs.particles.FX_STRINGS = o3djs.particles.FX_STRINGS_CG;
  if (o3djs.effect.LANGUAGE == 'glsl') {
    o3djs.particles.FX_STRINGS = o3djs.particles.FX_STRINGS_GLSL;
  }
};


/**
 * Corner values.
 * @private
 * @type {!Array.<!Array.<number>>}
 */
o3djs.particles.CORNERS_ = [
      [-0.5, -0.5],
      [+0.5, -0.5],
      [+0.5, +0.5],
      [-0.5, +0.5]];


/**
 * Creates a particle system.
 * You only need one of these to run multiple emitters of different types
 * of particles.
 * @param {!o3d.Pack} pack The pack for the particle system to manage resources.
 * @param {!o3djs.rendergraph.ViewInfo} viewInfo A viewInfo so the particle
 *     system can do the default setup. The only thing used from viewInfo
 *     is the zOrderedDrawList. If that is not where you want your particles,
 *     after you create the particleEmitter use
 *     particleEmitter.material.drawList = myDrawList to set it to something
 *     else.
 * @param {!o3d.ParamFloat} opt_clockParam A ParamFloat to be the default
 *     clock for emitters of this particle system.
 * @param {!function(): number} opt_randomFunction A function that returns
 *     a random number between 0.0 and 1.0. This allows you to pass in a
 *     pseudo random function if you need particles that are reproducable.
 * @return {!o3djs.particles.ParticleSystem} The created particle system.
 */
o3djs.particles.createParticleSystem = function(pack,
                                                viewInfo,
                                                opt_clockParam,
                                                opt_randomFunction) {
  return new o3djs.particles.ParticleSystem(pack,
                                            viewInfo,
                                            opt_clockParam,
                                            opt_randomFunction);
};

/**
 * An Object to manage Particles.
 * @constructor
 * @param {!o3d.Pack} pack The pack for the particle system to manage resources.
 * @param {!o3djs.rendergraph.ViewInfo} viewInfo A viewInfo so the particle
 *     system can do the default setup. The only thing used from viewInfo
 *     is the zOrderedDrawList. If that is not where you want your particles,
 *     after you create the particleEmitter use
 *     particleEmitter.material.drawList = myDrawList to set it to something
 *     else.
 * @param {!o3d.ParamFloat} opt_clockParam A ParamFloat to be the default
 *     clock for emitters of this particle system.
 * @param {!function(): number} opt_randomFunction A function that returns
 *     a random number between 0.0 and 1.0. This allows you to pass in a
 *     pseudo random function if you need particles that are reproducable.
 */
o3djs.particles.ParticleSystem = function(pack,
                                          viewInfo,
                                          opt_clockParam,
                                          opt_randomFunction) {
  var o3d = o3djs.base.o3d;
  var particleStates = [];
  var effects = [];
  o3djs.particles.useCorrectShaders_();
  for (var ee = 0; ee < o3djs.particles.FX_STRINGS.length; ++ee) {
    var info = o3djs.particles.FX_STRINGS[ee];
    var effect = pack.createObject('Effect');
    effect.name = info.name;
    effect.loadFromFXString(info.fxString);
    effects.push(effect);
  }

  var stateInfos = {};
  stateInfos[o3djs.particles.ParticleStateIds.BLEND] = {
    'SourceBlendFunction':
        o3djs.base.o3d.State.BLENDFUNC_SOURCE_ALPHA,
    'DestinationBlendFunction':
        o3djs.base.o3d.State.BLENDFUNC_INVERSE_SOURCE_ALPHA};

  stateInfos[o3djs.particles.ParticleStateIds.ADD] = {
    'SourceBlendFunction':
        o3djs.base.o3d.State.BLENDFUNC_SOURCE_ALPHA,
    'DestinationBlendFunction':
        o3djs.base.o3d.State.BLENDFUNC_ONE};

  stateInfos[o3djs.particles.ParticleStateIds.BLEND_PREMULTIPLY] = {
    'SourceBlendFunction':
        o3djs.base.o3d.State.BLENDFUNC_ONE,
    'DestinationBlendFunction':
        o3djs.base.o3d.State.BLENDFUNC_INVERSE_SOURCE_ALPHA};

  stateInfos[o3djs.particles.ParticleStateIds.BLEND_NO_ALPHA] = {
    'SourceBlendFunction':
        o3djs.base.o3d.State.BLENDFUNC_SOURCE_COLOR,
    'DestinationBlendFunction':
        o3djs.base.o3d.State.BLENDFUNC_INVERSE_SOURCE_COLOR};

  stateInfos[o3djs.particles.ParticleStateIds.SUBTRACT] = {
    'SourceBlendFunction':
        o3djs.base.o3d.State.BLENDFUNC_SOURCE_ALPHA,
    'DestinationBlendFunction':
        o3djs.base.o3d.State.BLENDFUNC_INVERSE_SOURCE_ALPHA,
    'BlendEquation':
        o3djs.base.o3d.State.BLEND_REVERSE_SUBTRACT};

  stateInfos[o3djs.particles.ParticleStateIds.INVERSE] = {
    'SourceBlendFunction':
        o3djs.base.o3d.State.BLENDFUNC_INVERSE_DESTINATION_COLOR,
    'DestinationBlendFunction':
        o3djs.base.o3d.State.BLENDFUNC_INVERSE_SOURCE_COLOR};

  for (var key in o3djs.particles.ParticleStateIds) {
    var state = pack.createObject('State');
    var id = o3djs.particles.ParticleStateIds[key];
    particleStates[id] = state;
    state.getStateParam('ZWriteEnable').value = false;
    state.getStateParam('CullMode').value = o3d.State.CULL_NONE;

    var info = stateInfos[id];
    for (var stateName in info) {
      state.getStateParam(stateName).value = info[stateName];
    }
  }

  var colorTexture = pack.createTexture2D(8, 8, o3d.Texture.ARGB8, 1, false);
  var pixelBase = [0, 0.20, 0.70, 1, 0.70, 0.20, 0, 0];
  var pixels = [];
  for (var yy = 0; yy < 8; ++yy) {
    for (var xx = 0; xx < 8; ++xx) {
      var pixel = pixelBase[xx] * pixelBase[yy];
      pixels.push(pixel, pixel, pixel, pixel);
    }
  }
  colorTexture.set(0, pixels);
  var rampTexture = pack.createTexture2D(3, 1, o3d.Texture.ARGB8, 1, false);
  rampTexture.set(0, [1, 1, 1, 1,
                      1, 1, 1, 0.5,
                      1, 1, 1, 0]);

  if (!opt_clockParam) {
    this.counter_ = pack.createObject('SecondCounter');
    opt_clockParam = this.counter_.getParam('count');
  }

  this.randomFunction_ = opt_randomFunction || function() {
        return Math.random();
      };

  /**
   * The states for the various blend modes.
   * @type {!Array.<!o3d.State>}
   */
  this.particleStates = particleStates;

  /**
   * The default ParamFloat to use as the clock for emitters created by
   * this system.
   * @type {!o3d.ParamFloat}
   */
  this.clockParam = opt_clockParam;

  /**
   * The pack used to manage particle system resources.
   * @type {!o3d.Pack}
   */
  this.pack = pack;

  /**
   * The viewInfo that is used to get drawLists.
   * @type {!o3djs.rendergraph.ViewInfo}
   */
  this.viewInfo = viewInfo;

  /**
   * The effects for particles.
   * @type {!Array.<!o3d.Effect>}
   */
  this.effects = effects;


  /**
   * The default color texture for particles.
   * @type {!o3d.Texture2D}
   */
  this.defaultColorTexture = colorTexture;


  /**
   * The default ramp texture for particles.
   * @type {!o3d.Texture2D}
   */
  this.defaultRampTexture = rampTexture;
};

/**
 * A ParticleSpec specifies how to emit particles.
 *
 * NOTE: For all particle functions you can specific a ParticleSpec as a
 * Javascript object, only specifying the fields that you care about.
 *
 * <pre>
 * emitter.setParameters({
 *   numParticles: 40,
 *   lifeTime: 2,
 *   timeRange: 2,
 *   startSize: 50,
 *   endSize: 90,
 *   positionRange: [10, 10, 10],
 *   velocity:[0, 0, 60], velocityRange: [15, 15, 15],
 *   acceleration: [0, 0, -20],
 *   spinSpeedRange: 4}
 * );
 * </pre>
 *
 * Many of these parameters are in pairs. For paired paramters each particle
 * specfic value is set like this
 *
 * particle.field = value + Math.random() - 0.5 * valueRange * 2;
 *
 * or in English
 *
 * particle.field = value plus or minus valueRange.
 *
 * So for example, if you wanted a value from 10 to 20 you'd pass 15 for value
 * and 5 for valueRange because
 *
 * 15 + or - 5  = (10 to 20)
 *
 * @constructor
 */
o3djs.particles.ParticleSpec = function() {
  /**
   * The number of particles to emit.
   * @type {number}
   */
  this.numParticles = 1;

  /**
   * The number of frames in the particle texture.
   * @type {number}
   */
  this.numFrames = 1;

  /**
   * The frame duration at which to animate the particle texture in seconds per
   * frame.
   * @type {number}
   */
  this.frameDuration = 1;

  /**
   * The initial frame to display for a particular particle.
   * @type {number}
   */
  this.frameStart = 0;

  /**
   * The frame start range.
   * @type {number}
   */
  this.frameStartRange = 0;

  /**
   * The life time of the entire particle system.
   * To make a particle system be continuous set this to match the lifeTime.
   * @type {number}
   */
  this.timeRange = 99999999;

  /**
   * The startTime of a particle.
   * @type {?number}
   */
  this.startTime = null;
  // TODO: Describe what happens if this is not set. I still have some
  //     work to do there.

  /**
   * The lifeTime of a particle.
   * @type {number}
   */
  this.lifeTime = 1;

  /**
   * The lifeTime range.
   * @type {number}
   */
  this.lifeTimeRange = 0;

  /**
   * The starting size of a particle.
   * @type {number}
   */
  this.startSize = 1;

  /**
   * The starting size range.
   * @type {number}
   */
  this.startSizeRange = 0;

  /**
   * The ending size of a particle.
   * @type {number}
   */
  this.endSize = 1;

  /**
   * The ending size range.
   * @type {number}
   */
  this.endSizeRange = 0;

  /**
   * The starting position of a particle in local space.
   * @type {!o3djs.math.Vector3}
   */
  this.position = [0, 0, 0];

  /**
   * The starting position range.
   * @type {!o3djs.math.Vector3}
   */
  this.positionRange = [0, 0, 0];

  /**
   * The velocity of a paritcle in local space.
   * @type {!o3djs.math.Vector3}
   */
  this.velocity = [0, 0, 0];

  /**
   * The velocity range.
   * @type {!o3djs.math.Vector3}
   */
  this.velocityRange = [0, 0, 0];

  /**
   * The acceleration of a particle in local space.
   * @type {!o3djs.math.Vector3}
   */
  this.acceleration = [0, 0, 0];

  /**
   * The accleration range.
   * @type {!o3djs.math.Vector3}
   */
  this.accelerationRange = [0, 0, 0];

  /**
   * The starting spin value for a particle in radians.
   * @type {number}
   */
  this.spinStart = 0;

  /**
   * The spin start range.
   * @type {number}
   */
  this.spinStartRange = 0;

  /**
   * The spin speed of a particle in radians.
   * @type {number}
   */
  this.spinSpeed = 0;

  /**
   * The spin speed range.
   * @type {number}
   */
  this.spinSpeedRange = 0;

  /**
   * The color multiplier of a particle.
   * @type {!o3djs.math.Vector4}
   */
  this.colorMult = [1, 1, 1, 1];

  /**
   * The color multiplier range.
   * @type {!o3djs.math.Vector4}
   */
  this.colorMultRange = [0, 0, 0, 0];

  /**
   * The velocity of all paritcles in world space.
   * @type {!o3djs.math.Vector3}
   */
  this.worldVelocity = [0, 0, 0];

  /**
   * The acceleration of all paritcles in world space.
   * @type {!o3djs.math.Vector3}
   */
  this.worldAcceleration = [0, 0, 0];

  /**
   * Whether these particles are oriented in 2d or 3d. true = 2d, false = 3d.
   * @type {boolean}
   */
  this.billboard = true;

  /**
   * The orientation of a particle. This is only used if billboard is false.
   * @type {!o3djs.quaternions.Quaternion}
   */
  this.orientation = [0, 0, 0, 1];
};

/**
 * Creates a particle emitter.
 * @param {!o3d.Texture} opt_texture The texture to use for the particles.
 *     If you don't supply a texture a default is provided.
 * @param {!o3d.ParamFloat} opt_clockParam A ParamFloat to be the clock for
 *     the emitter.
 * @return {!o3djs.particles.ParticleEmitter} The new emitter.
 */
o3djs.particles.ParticleSystem.prototype.createParticleEmitter =
    function(opt_texture, opt_clockParam) {
  return new o3djs.particles.ParticleEmitter(this, opt_texture, opt_clockParam);
};

/**
 * Creates a Trail particle emitter.
 * You can use this for jet exhaust, etc...
 * @param {!o3d.Transform} parent Transform to put emitter on.
 * @param {number} maxParticles Maximum number of particles to appear at once.
 * @param {!o3djs.particles.ParticleSpec} parameters The parameters used to
 *     generate particles.
 * @param {!o3d.Texture} opt_texture The texture to use for the particles.
 *     If you don't supply a texture a default is provided.
 * @param {!function(number, !o3djs.particles.ParticleSpec): void}
 *     opt_perParticleParamSetter A function that is called for each particle to
 *     allow it's parameters to be adjusted per particle. The number is the
 *     index of the particle being created, in other words, if numParticles is
 *     20 this value will be 0 to 19. The ParticleSpec is a spec for this
 *     particular particle. You can set any per particle value before returning.
 * @param {!o3d.ParamFloat} opt_clockParam A ParamFloat to be the clock for
 *     the emitter.
 * @return {!o3djs.particles.Trail} A Trail object.
 */
o3djs.particles.ParticleSystem.prototype.createTrail = function(
    parent,
    maxParticles,
    parameters,
    opt_texture,
    opt_perParticleParamSetter,
    opt_clockParam) {
  return new o3djs.particles.Trail(
      this,
      parent,
      maxParticles,
      parameters,
      opt_texture,
      opt_perParticleParamSetter,
      opt_clockParam);
};

/**
 * A ParticleEmitter
 * @constructor
 * @param {!o3djs.particles.ParticleSystem} particleSystem The particle system
 *     to manage this emitter.
 * @param {!o3d.Texture} opt_texture The texture to use for the particles.
 *     If you don't supply a texture a default is provided.
 * @param {!o3d.ParamFloat} opt_clockParam A ParamFloat to be the clock for
 *     the emitter.
 */
o3djs.particles.ParticleEmitter = function(particleSystem,
                                           opt_texture,
                                           opt_clockParam) {
  opt_clockParam = opt_clockParam || particleSystem.clockParam;

  var o3d = o3djs.base.o3d;
  var pack = particleSystem.pack;
  var viewInfo = particleSystem.viewInfo;
  var material = pack.createObject('Material');
  material.name = 'particles';
  material.drawList = viewInfo.zOrderedDrawList;
  material.effect = particleSystem.effects[1];
  particleSystem.effects[1].createUniformParameters(material);
  material.getParam('time').bind(opt_clockParam);

  var rampSampler = pack.createObject('Sampler');
  rampSampler.texture = particleSystem.defaultRampTexture;
  rampSampler.addressModeU = o3d.Sampler.CLAMP;

  var colorSampler = pack.createObject('Sampler');
  colorSampler.texture = opt_texture || particleSystem.defaultColorTexture;
  colorSampler.addressModeU = o3d.Sampler.CLAMP;
  colorSampler.addressModeV = o3d.Sampler.CLAMP;

  material.getParam('rampSampler').value = rampSampler;
  material.getParam('colorSampler').value = colorSampler;

  var vertexBuffer = pack.createObject('VertexBuffer');
  var uvLifeTimeFrameStartField = vertexBuffer.createField('FloatField', 4);
  var positionStartTimeField = vertexBuffer.createField('FloatField', 4);
  var velocityStartSizeField = vertexBuffer.createField('FloatField', 4);
  var accelerationEndSizeField = vertexBuffer.createField('FloatField', 4);
  var spinStartSpinSpeedField = vertexBuffer.createField('FloatField', 4);
  var orientationField = vertexBuffer.createField('FloatField', 4);
  var colorMultField = vertexBuffer.createField('FloatField', 4);

  var indexBuffer = pack.createObject('IndexBuffer');

  var streamBank = pack.createObject('StreamBank');
  streamBank.setVertexStream(o3d.Stream.POSITION, 0,
                             uvLifeTimeFrameStartField, 0);
  streamBank.setVertexStream(o3d.Stream.TEXCOORD, 0,
                             positionStartTimeField, 0);
  streamBank.setVertexStream(o3d.Stream.TEXCOORD, 1,
                             velocityStartSizeField, 0);
  streamBank.setVertexStream(o3d.Stream.TEXCOORD, 2,
                             accelerationEndSizeField, 0);
  streamBank.setVertexStream(o3d.Stream.TEXCOORD, 3,
                             spinStartSpinSpeedField, 0);
  streamBank.setVertexStream(o3d.Stream.TEXCOORD, 4,
                             orientationField, 0);
  streamBank.setVertexStream(o3d.Stream.COLOR, 0,
                             colorMultField, 0);

  var shape = pack.createObject('Shape');
  var primitive = pack.createObject('Primitive');
  primitive.material = material;
  primitive.owner = shape;
  primitive.streamBank = streamBank;
  primitive.indexBuffer = indexBuffer;
  primitive.primitiveType = o3d.Primitive.TRIANGLELIST;
  primitive.createDrawElement(pack, null);

  this.vertexBuffer_ = vertexBuffer;
  this.uvLifeTimeFrameStartField_ = uvLifeTimeFrameStartField;
  this.positionStartTimeField_ = positionStartTimeField;
  this.velocityStartSizeField_ = velocityStartSizeField;
  this.accelerationEndSizeField_ = accelerationEndSizeField;
  this.spinStartSpinSpeedField_ = spinStartSpinSpeedField;
  this.orientationField_ = orientationField;
  this.colorMultField_ = colorMultField;
  this.indexBuffer_ = indexBuffer;
  this.streamBank_ = streamBank;
  this.primitive_ = primitive;
  this.rampSampler_ = rampSampler;
  this.rampTexture_ = particleSystem.defaultRampTexture;
  this.colorSampler_ = colorSampler;

  /**
   * The particle system managing this emitter.
   * @type {!o3djs.particles.ParticleSystem}
   */
  this.particleSystem = particleSystem;

  /**
   * The Shape used to render these particles.
   * @type {!o3d.Shape}
   */
  this.shape = shape;

  /**
   * The material used by this emitter.
   * @type {!o3d.Material}
   */
  this.material = material;

  /**
   * The param that is the source for the time for this emitter.
   * @type {!o3d.ParamFloat}
   */
  this.clockParam = opt_clockParam;
};

/**
 * Sets the blend state for the particles.
 * You can use this to set the emitter to draw with BLEND, ADD, SUBTRACT, etc.
 * @param {o3djs.particles.ParticleStateIds} stateId The state you want.
 */
o3djs.particles.ParticleEmitter.prototype.setState = function(stateId) {
  this.material.state = this.particleSystem.particleStates[stateId];
};

/**
 * Sets the colorRamp for the particles.
 * The colorRamp is used as a multiplier for the texture. When a particle
 * starts it is multiplied by the first color, as it ages to progressed
 * through the colors in the ramp.
 *
 * <pre>
 * particleEmitter.setColorRamp([
 *   1, 0, 0, 1,    // red
 *   0, 1, 0, 1,    // green
 *   1, 0, 1, 0]);  // purple but with zero alpha
 * </pre>
 *
 * The code above sets the particle to start red, change to green then
 * fade out while changing to purple.
 *
 * @param {!Array.<number>} colorRamp An array of color values in
 *     the form RGBA.
 */
o3djs.particles.ParticleEmitter.prototype.setColorRamp = function(colorRamp) {
  var width = colorRamp.length / 4;
  if (width % 1 != 0) {
    throw 'colorRamp must have multiple of 4 entries';
  }

  if (this.rampTexture_ == this.particleSystem.defaultRampTexture) {
    this.rampTexture_ = null;
  }

  if (this.rampTexture_ && this.rampTexture_.width != width) {
    this.particleSystem.pack.removeObject(this.rampTexture_);
    this.rampTexture_ = null;
  }

  if (!this.rampTexture_) {
    this.rampTexture_ = this.particleSystem.pack.createTexture2D(
        width, 1, o3djs.base.o3d.Texture.ARGB8, 1, false);
  }

  this.rampTexture_.set(0, colorRamp);
  this.rampSampler_.texture = this.rampTexture_;
};

/**
 * Validates and adds missing particle parameters.
 * @param {!o3djs.particles.ParticleSpec} parameters The parameters to validate.
 */
o3djs.particles.ParticleEmitter.prototype.validateParameters = function(
    parameters) {
  var defaults = new o3djs.particles.ParticleSpec();
  for (var key in parameters) {
    if (typeof defaults[key] === 'undefined') {
      throw 'unknown particle parameter "' + key + '"';
    }
  }
  for (var key in defaults) {
    if (typeof parameters[key] === 'undefined') {
      parameters[key] = defaults[key];
    }
  }
};

/**
 * Creates particles.
 * @private
 * @param {number} firstParticleIndex Index of first particle to create.
 * @param {number} numParticles The number of particles to create.
 * @param {!o3djs.particles.ParticleSpec} parameters The parameters for the
 *     emitters.
 * @param {!function(number, !o3djs.particles.ParticleSpec): void}
 *     opt_perParticleParamSetter A function that is called for each particle to
 *     allow it's parameters to be adjusted per particle. The number is the
 *     index of the particle being created, in other words, if numParticles is
 *     20 this value will be 0 to 19. The ParticleSpec is a spec for this
 *     particular particle. You can set any per particle value before returning.
 */
o3djs.particles.ParticleEmitter.prototype.createParticles_ = function(
    firstParticleIndex,
    numParticles,
    parameters,
    opt_perParticleParamSetter) {
  var uvLifeTimeFrameStart = this.uvLifeTimeFrameStart_;
  var positionStartTime = this.positionStartTime_;
  var velocityStartSize = this.velocityStartSize_;
  var accelerationEndSize = this.accelerationEndSize_;
  var spinStartSpinSpeed = this.spinStartSpinSpeed_;
  var orientation = this.orientation_;
  var colorMults = this.colorMults_;

  // Set the globals.
  this.material.effect =
      this.particleSystem.effects[parameters.billboard ? 1 : 0];
  this.material.getParam('timeRange').value = parameters.timeRange;
  this.material.getParam('numFrames').value = parameters.numFrames;
  this.material.getParam('frameDuration').value = parameters.frameDuration;
  this.material.getParam('worldVelocity').value = parameters.worldVelocity;
  this.material.getParam('worldAcceleration').value =
      parameters.worldAcceleration;

  var random = this.particleSystem.randomFunction_;

  var plusMinus = function(range) {
    return (random() - 0.5) * range * 2;
  };

  // TODO: change to not allocate.
  var plusMinusVector = function(range) {
    var v = [];
    for (var ii = 0; ii < range.length; ++ii) {
      v.push(plusMinus(range[ii]));
    }
    return v;
  };

  for (var ii = 0; ii < numParticles; ++ii) {
    if (opt_perParticleParamSetter) {
      opt_perParticleParamSetter(ii, parameters);
    }
    var pLifeTime = parameters.lifeTime;
    var pStartTime = (parameters.startTime === null) ?
        (ii * parameters.lifeTime / numParticles) : parameters.startTime;
    var pFrameStart =
        parameters.frameStart + plusMinus(parameters.frameStartRange);
    var pPosition = o3djs.math.addVector(
        parameters.position, plusMinusVector(parameters.positionRange));
    var pVelocity = o3djs.math.addVector(
        parameters.velocity, plusMinusVector(parameters.velocityRange));
    var pAcceleration = o3djs.math.addVector(
        parameters.acceleration,
        plusMinusVector(parameters.accelerationRange));
    var pColorMult = o3djs.math.addVector(
        parameters.colorMult, plusMinusVector(parameters.colorMultRange));
    var pSpinStart =
        parameters.spinStart + plusMinus(parameters.spinStartRange);
    var pSpinSpeed =
        parameters.spinSpeed + plusMinus(parameters.spinSpeedRange);
    var pStartSize =
        parameters.startSize + plusMinus(parameters.startSizeRange);
    var pEndSize = parameters.endSize + plusMinus(parameters.endSizeRange);
    var pOrientation = parameters.orientation;

    // make each corner of the particle.
    for (var jj = 0; jj < 4; ++jj) {
      var offset0 = (ii * 4 + jj) * 4;
      var offset1 = offset0 + 1;
      var offset2 = offset0 + 2;
      var offset3 = offset0 + 3;

      uvLifeTimeFrameStart[offset0] = o3djs.particles.CORNERS_[jj][0];
      uvLifeTimeFrameStart[offset1] = o3djs.particles.CORNERS_[jj][1];
      uvLifeTimeFrameStart[offset2] = pLifeTime;
      uvLifeTimeFrameStart[offset3] = pFrameStart;

      positionStartTime[offset0] = pPosition[0];
      positionStartTime[offset1] = pPosition[1];
      positionStartTime[offset2] = pPosition[2];
      positionStartTime[offset3] = pStartTime;

      velocityStartSize[offset0] = pVelocity[0];
      velocityStartSize[offset1] = pVelocity[1];
      velocityStartSize[offset2] = pVelocity[2];
      velocityStartSize[offset3] = pStartSize;

      accelerationEndSize[offset0] = pAcceleration[0];
      accelerationEndSize[offset1] = pAcceleration[1];
      accelerationEndSize[offset2] = pAcceleration[2];
      accelerationEndSize[offset3] = pEndSize;

      spinStartSpinSpeed[offset0] = pSpinStart;
      spinStartSpinSpeed[offset1] = pSpinSpeed;
      spinStartSpinSpeed[offset2] = 0;
      spinStartSpinSpeed[offset3] = 0;

      orientation[offset0] = pOrientation[0];
      orientation[offset1] = pOrientation[1];
      orientation[offset2] = pOrientation[2];
      orientation[offset3] = pOrientation[3];

      colorMults[offset0] = pColorMult[0];
      colorMults[offset1] = pColorMult[1];
      colorMults[offset2] = pColorMult[2];
      colorMults[offset3] = pColorMult[3];
    }
  }

  firstParticleIndex *= 4;
  this.uvLifeTimeFrameStartField_.setAt(
      firstParticleIndex,
      uvLifeTimeFrameStart);
  this.positionStartTimeField_.setAt(
      firstParticleIndex,
      positionStartTime);
  this.velocityStartSizeField_.setAt(
      firstParticleIndex,
      velocityStartSize);
  this.accelerationEndSizeField_.setAt(
      firstParticleIndex,
      accelerationEndSize);
  this.spinStartSpinSpeedField_.setAt(
      firstParticleIndex,
      spinStartSpinSpeed);
  this.orientationField_.setAt(
      firstParticleIndex,
      orientation);
  this.colorMultField_.setAt(
      firstParticleIndex,
      colorMults);
};

/**
 * Allocates particles.
 * @private
 * @param {number} numParticles Number of particles to allocate.
 */
o3djs.particles.ParticleEmitter.prototype.allocateParticles_ = function(
    numParticles) {
  if (this.vertexBuffer_.numElements != numParticles * 4) {
    this.vertexBuffer_.allocateElements(numParticles * 4);

    var indices = [];
    for (var ii = 0; ii < numParticles; ++ii) {
      // Make 2 triangles for the quad.
      var startIndex = ii * 4
      indices.push(startIndex + 0, startIndex + 1, startIndex + 2);
      indices.push(startIndex + 0, startIndex + 2, startIndex + 3);
    }
    this.indexBuffer_.set(indices);

    // We keep these around to avoid memory allocations for trails.
    this.uvLifeTimeFrameStart_ = [];
    this.positionStartTime_ = [];
    this.velocityStartSize_ = [];
    this.accelerationEndSize_ = [];
    this.spinStartSpinSpeed_ = [];
    this.orientation_ = [];
    this.colorMults_ = [];
  }

  this.primitive_.numberPrimitives = numParticles * 2;
  this.primitive_.numberVertices = numParticles * 4;
};

/**
 * Sets the parameters of the particle emitter.
 *
 * Each of these parameters are in pairs. The used to create a table
 * of particle parameters. For each particle a specfic value is
 * set like this
 *
 * particle.field = value + Math.random() - 0.5 * valueRange * 2;
 *
 * or in English
 *
 * particle.field = value plus or minus valueRange.
 *
 * So for example, if you wanted a value from 10 to 20 you'd pass 15 for value
 * and 5 for valueRange because
 *
 * 15 + or - 5  = (10 to 20)
 *
 * @param {!o3djs.particles.ParticleSpec} parameters The parameters for the
 *     emitters.
 * @param {!function(number, !o3djs.particles.ParticleSpec): void}
 *     opt_perParticleParamSetter A function that is called for each particle to
 *     allow it's parameters to be adjusted per particle. The number is the
 *     index of the particle being created, in other words, if numParticles is
 *     20 this value will be 0 to 19. The ParticleSpec is a spec for this
 *     particular particle. You can set any per particle value before returning.
 */
o3djs.particles.ParticleEmitter.prototype.setParameters = function(
    parameters,
    opt_perParticleParamSetter) {
  this.validateParameters(parameters);

  var numParticles = parameters.numParticles;

  this.allocateParticles_(numParticles);

  this.createParticles_(
      0,
      numParticles,
      parameters,
      opt_perParticleParamSetter);
};

/**
 * Creates a OneShot particle emitter instance.
 * You can use this for dust puffs, explosions, fireworks, etc...
 * @param {!o3d.Transform} opt_parent The parent for the oneshot.
 * @return {!o3djs.particles.OneShot} A OneShot object.
 */
o3djs.particles.ParticleEmitter.prototype.createOneShot = function(opt_parent) {
  return new o3djs.particles.OneShot(this, opt_parent);
};

/**
 * An object to manage a particle emitter instance as a one shot. Examples of
 * one shot effects are things like an explosion, some fireworks.
 * @constructor
 * @param {!o3djs.particles.ParticleEmitter} emitter The emitter to use for the
 *     one shot.
 * @param {!o3d.Transform} opt_parent The parent for this one shot.
 */
o3djs.particles.OneShot = function(emitter, opt_parent) {
  var pack = emitter.particleSystem.pack;
  this.emitter_ = emitter;

  /**
   * Transform for OneShot.
   * @type {!o3d.Transform}
   */
  this.transform = pack.createObject('Transform');
  this.transform.visible = false;
  this.transform.addShape(emitter.shape);
  this.timeOffsetParam_ =
      this.transform.createParam('timeOffset', 'ParamFloat');
  if (opt_parent) {
    this.setParent(opt_parent);
  }
};

/**
 * Sets the parent transform for this OneShot.
 * @param {!o3d.Transform} parent The parent for this one shot.
 */
o3djs.particles.OneShot.prototype.setParent = function(parent) {
  this.transform.parent = parent;
};

/**
 * Triggers the oneshot.
 *
 * Note: You must have set the parent either at creation, with setParent, or by
 * passing in a parent here.
 *
 * @param {!o3djs.math.Vector3} opt_position The position of the one shot
 *     relative to its parent.
 * @param {!o3d.Transform} opt_parent The parent for this one shot.
 */
o3djs.particles.OneShot.prototype.trigger = function(opt_position, opt_parent) {
  if (opt_parent) {
    this.setParent(opt_parent);
  }
  if (opt_position) {
    this.transform.identity();
    this.transform.translate(opt_position);
  }
  this.transform.visible = true;
  this.timeOffsetParam_.value = this.emitter_.clockParam.value;
};

/**
 * A type of emitter to use for particle effects that leave trails like exhaust.
 * @constructor
 * @extends {o3djs.particles.ParticleEmitter}
 * @param {!o3djs.particles.ParticleSystem} particleSystem The particle system
 *     to manage this emitter.
 * @param {!o3d.Transform} parent Transform to put emitter on.
 * @param {number} maxParticles Maximum number of particles to appear at once.
 * @param {!o3djs.particles.ParticleSpec} parameters The parameters used to
 *     generate particles.
 * @param {!o3d.Texture} opt_texture The texture to use for the particles.
 *     If you don't supply a texture a default is provided.
 * @param {!function(number, !o3djs.particles.ParticleSpec): void}
 *     opt_perParticleParamSetter A function that is called for each particle to
 *     allow it's parameters to be adjusted per particle. The number is the
 *     index of the particle being created, in other words, if numParticles is
 *     20 this value will be 0 to 19. The ParticleSpec is a spec for this
 *     particular particle. You can set any per particle value before returning.
 * @param {!o3d.ParamFloat} opt_clockParam A ParamFloat to be the clock for
 *     the emitter.
 */
o3djs.particles.Trail = function(
    particleSystem,
    parent,
    maxParticles,
    parameters,
    opt_texture,
    opt_perParticleParamSetter,
    opt_clockParam) {
  o3djs.particles.ParticleEmitter.call(
      this, particleSystem, opt_texture, opt_clockParam);

  var pack = particleSystem.pack;

  this.allocateParticles_(maxParticles);
  this.validateParameters(parameters);

  this.parameters = parameters;
  this.perParticleParamSetter = opt_perParticleParamSetter;
  this.birthIndex_ = 0;
  this.maxParticles_ = maxParticles;

  /**
   * Transform for OneShot.
   * @type {!o3d.Transform}
   */
  this.transform = pack.createObject('Transform');
  this.transform.addShape(this.shape);

  this.transform.parent = parent;
};

o3djs.base.inherit(o3djs.particles.Trail, o3djs.particles.ParticleEmitter);

/**
 * Births particles from this Trail.
 * @param {!o3djs.math.Vector3} position Position to birth particles at.
 */
o3djs.particles.Trail.prototype.birthParticles = function(position) {
  var numParticles = this.parameters.numParticles;
  this.parameters.startTime = this.clockParam.value;
  this.parameters.position = position;
  while (this.birthIndex_ + numParticles >= this.maxParticles_) {
    var numParticlesToEnd = this.maxParticles_ - this.birthIndex_;
    this.createParticles_(this.birthIndex_,
                          numParticlesToEnd,
                          this.parameters,
                          this.perParticleParamSetter);
    numParticles -= numParticlesToEnd;
    this.birthIndex_ = 0;
  }
  this.createParticles_(this.birthIndex_,
                        numParticles,
                        this.parameters,
                        this.perParticleParamSetter);
  this.birthIndex_ += numParticles;
};


/*
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview This file contains functions for implementing picking.
 * It puts them in the "picking" module on the o3djs object.
 *
 *
 * This example shows one way to implement picking. Because O3D is shader
 * agnostic we can't handle picking automatically since we have no way of
 * knowing what the developer is going to do with their shaders. On the other
 * hand, we can provide various functions that make it possible to do your own
 * picking. Only you know which objects are pickable and which are not. For
 * example if you are making an RTS game, only you would know that units are
 * pickable but ground and explosions are not and that neither is your HUD.
 * Similarly, only you would know how your shaders manipulate the vertices
 * passed to them.
 *
 * It's possible that someone, maybe us, will create an engine to use o3d
 * that given a bunch of restrictions and flags on the data it accepts can
 * do picking in a more automatic way but that is not the goal of the o3d
 * api. Its goal is to provide a LOW-LEVEL shader agnostic API.
 */

o3djs.provide('o3djs.picking');

/**
 * A Module for picking.
 * @namespace
 */
o3djs.picking = o3djs.picking || {};

/**
 * A ray.
 * @type {{near: !o3djs.math.Vector3, far: !o3djs.math.Vector3}}
 */
o3djs.picking.Ray = goog.typedef;

/**
 * Creates a new PickInfo.
 * @param {!o3d.Element} element The Element that was picked.
 * @param {!o3djs.picking.ShapeInfo} shapeInfo The ShapeInfo that was picked.
 * @param {!o3d.RayIntersectionInfo} rayIntersectionInfo Information about the
 *     pick.
 * @param {!o3djs.math.Vector3} worldIntersectionPosition world position of
 *     intersection.
 * @return {!o3djs.picking.PickInfo} The new PickInfo.
 */
o3djs.picking.createPickInfo = function(element,
                                        shapeInfo,
                                        rayIntersectionInfo,
                                        worldIntersectionPosition) {
  return new o3djs.picking.PickInfo(element,
                                    shapeInfo,
                                    rayIntersectionInfo,
                                    worldIntersectionPosition);
};

/**
 * Convert a pixel position relative to the top left corner of the client area
 * into the corresponding ray through the frustum in world space.
 * @param {number} clientXPosition x position relative to client area.
 * @param {number} clientYPosition y position relative to client area.
 * @param {!o3djs.math.Matrix4} view View matrix to transform with.
 * @param {!o3djs.math.Matrix4} projection Projection matrix to transform
 *     with.
 * @param {number} clientWidth width of client area.
 * @param {number} clientHeight height of client area.
 * @return {!o3djs.picking.Ray} ray in world space.
 */
o3djs.picking.clientPositionToWorldRayEx = function(clientXPosition,
                                                    clientYPosition,
                                                    view,
                                                    projection,
                                                    clientWidth,
                                                    clientHeight) {
  // compute the world position of a ray going through the view frustum
  var inverseViewProjectionMatrix = o3djs.math.inverse(
      o3djs.math.matrix4.composition(projection, view));
  // normScreenX, normScreenY are in frustum coordinates.
  var normScreenX = clientXPosition / (clientWidth * 0.5) - 1;
  var normScreenY = -(clientYPosition / (clientHeight * 0.5) - 1);

  // Apply inverse view-projection matrix to get the ray in world coordinates.
  return {
      near: o3djs.math.matrix4.transformPoint(
          inverseViewProjectionMatrix, [normScreenX, normScreenY, 0]),
      far: o3djs.math.matrix4.transformPoint(
          inverseViewProjectionMatrix, [normScreenX, normScreenY, 1])
  };
};

/**
 * Convert a pixel position relative to the top left corner of the client area
 * into the corresponding ray through the frustum in world space.
 * @param {number} clientXPosition x position relative to client area.
 * @param {number} clientYPosition y position relative to client area.
 * @param {!o3d.DrawContext} drawContext DrawContext to get view and
 *     projection matrices from.
 * @param {number} clientWidth width of client area.
 * @param {number} clientHeight height of client area.
 * @return {!o3djs.picking.Ray} ray in world space.
 */
o3djs.picking.clientPositionToWorldRay = function(clientXPosition,
                                                  clientYPosition,
                                                  drawContext,
                                                  clientWidth,
                                                  clientHeight) {
  return o3djs.picking.clientPositionToWorldRayEx(
      clientXPosition,
      clientYPosition,
      drawContext.view,
      drawContext.projection,
      clientWidth,
      clientHeight);
};

/**
 * A local dump function so we can easily comment it out.
 * @param {string} msg Message to dump.
 */
o3djs.picking.dprint = function(msg) {
  //o3djs.dump.dump(msg);
};

/**
 * A local dump function so we can easily comment it out.
 * @param {string} label Label to print before value.
 * @param {!o3djs.math.Vector3} float3 Value to print.
 * @param {string} prefix optional prefix for indenting.
 */
o3djs.picking.dprintPoint3 = function(label, float3, prefix) {
  //o3djs.dump.dumpPoint3(label, float3, prefix);
};

/**
 * A local dump function so we can easily comment it out.
 * @param {string} label Label to put in front of dump.
 * @param {!o3d.BoundingBox} boundingBox BoundingBox to dump.
 * @param {string} opt_prefix optional prefix for indenting.
 */
o3djs.picking.dprintBoundingBox = function(label,
                                           boundingBox,
                                           opt_prefix) {
  //o3djs.dump.dumpBoundingBox(label, boundingBox, opt_prefix);
};

/**
 * A local dump function so we can easily comment it out.
 * @param {string} label Label to print before value.
 * @param {!o3d.RayIntersectionInfo} rayIntersectionInfo Value to print.
 */
o3djs.picking.dumpRayIntersectionInfo = function(label,
                                                 rayIntersectionInfo) {
  o3djs.picking.dprint(
      label + ' : valid = ' +
      rayIntersectionInfo.valid + ' : intersected = ' +
      rayIntersectionInfo.intersected);
  if (rayIntersectionInfo.intersected) {
    o3djs.picking.dprint(
        ' : pos: ' +
        rayIntersectionInfo.position[0] + ', ' +
        rayIntersectionInfo.position[1] + ', ' +
        rayIntersectionInfo.position[2] + ', ');
  }
  o3djs.picking.dprint('\n');
};

/**
 * Creates a new PickInfo. Used to return picking information.
 * @constructor
 * @param {!o3d.Element} element The Element that was picked.
 * @param {!o3djs.picking.ShapeInfo} shapeInfo The ShapeInfo that was picked.
 * @param {!o3d.RayIntersectionInfo} rayIntersectionInfo Information about the
 *     pick.
 * @param {!o3djs.math.Vector3} worldIntersectionPosition world position of
 *     intersection.
 */
o3djs.picking.PickInfo = function(element,
                                  shapeInfo,
                                  rayIntersectionInfo,
                                  worldIntersectionPosition) {
  /**
   * The Element that was picked (Primitive).
   * @type {!o3d.Element}
   */
  this.element = element;

  /**
   * The ShapeInfo that was picked.
   * @type {!o3djs.picking.ShapeInfo}
   */
  this.shapeInfo = shapeInfo;

  /**
   * Information about the pick.
   * @type {!o3d.RayIntersectionInfo}
   */
  this.rayIntersectionInfo = rayIntersectionInfo;

  /**
   * The worldIntersectionPosition world position of intersection.
   * @type {!o3djs.math.Vector3}
   */
  this.worldIntersectionPosition = worldIntersectionPosition
};

/**
 * Creates a new ShapeInfo. Used to store information about Shapes. Note, even
 * though Shapes can be instanced, ShapeInfos can not so if a Shape is instanced
 * there will be more than one ShapeInfo for it.
 * @constructor
 * @param {!o3d.Shape} shape Shape to keep info about.
 * @param {!o3djs.picking.TransformInfo} parent Parent transform of the shape.
 * @param {!o3djs.picking.PickManager} pickManager The PickManager this
 *     ShapeInfo belongs to.
 */
o3djs.picking.ShapeInfo = function(shape, parent, pickManager) {
  /**
   * The Shape for this ShapeInfo
   * @type {!o3d.Shape}
   */
  this.shape = shape;

  /**
   * The parent TransformInfo of this Shape.
   * @type {!o3djs.picking.TransformInfo}
   */
  this.parent = parent;

  /**
   * The bounding box for this Shape
   * @type {o3d.BoundingBox}
   */
  this.boundingBox = null;

  /**
   * The PickManager this ShapeInfo belongs to.
   * @type {!o3djs.picking.PickManager}
   */
  this.pickManager = pickManager;

  this.update();
};

/**
 * Returns whether or not this ShapeInfo is pickable.
 * @return {boolean} Whether or not this ShapeInfo is pickable.
 */
o3djs.picking.ShapeInfo.prototype.isPickable = function() {
  return true;
}

/**
 * Gets the BoundingBox of the Shape in this ShapeInfo.
 * @return {o3d.BoundingBox} The Shape's BoundingBox.
 */
o3djs.picking.ShapeInfo.prototype.getBoundingBox = function() {
  return this.boundingBox;
};

/**
 * Updates the BoundingBox of the Shape in this ShapeInfo.
 */
o3djs.picking.ShapeInfo.prototype.update = function() {
  var elements = this.shape.elements;
  if (elements.length > 0) {
    this.boundingBox = elements[0].getBoundingBox(0);
    for (var ee = 1; ee < elements.length; ee++) {
      this.boundingBox = this.boundingBox.add(elements[ee].getBoundingBox(0));
    }
  }
};

/**
 * Attempts to "pick" this Shape by checking for the intersection of a ray
 * in world space to the triangles this shape uses.
 * @param {!o3djs.picking.Ray} worldRay A ray in world space to pick against.
 * @return {o3djs.picking.PickInfo} Information about the picking.
 *     null if the ray did not intersect any triangles.
 */
o3djs.picking.ShapeInfo.prototype.pick = function(worldRay) {
  if (this.isPickable()) {
    var worldMatrix = this.parent.transform.getUpdatedWorldMatrix()
    var inverseWorldMatrix = o3djs.math.inverse(worldMatrix);
    var relativeNear = o3djs.math.matrix4.transformPoint(
        inverseWorldMatrix, worldRay.near);
    var relativeFar = o3djs.math.matrix4.transformPoint(
        inverseWorldMatrix, worldRay.far);
    var rayIntersectionInfo =
        this.boundingBox.intersectRay(relativeNear,
                                      relativeFar);

    o3djs.picking.dumpRayIntersectionInfo('SHAPE(box): ' + this.shape.name,
                                          rayIntersectionInfo);

    if (rayIntersectionInfo.intersected) {
      var elements = this.shape.elements;
      for (var e = 0; e < elements.length; e++) {
        var element = elements[e];
        rayIntersectionInfo = element.intersectRay(
            0,
            o3djs.base.o3d.State.CULL_CCW,
            relativeNear,
            relativeFar);
        o3djs.picking.dumpRayIntersectionInfo(
            'SHAPE(tris): ' + this.shape.name + ' : element ' + element.name,
            rayIntersectionInfo);

        // TODO: get closest element not just first element.
        if (rayIntersectionInfo.intersected) {
          var worldIntersectionPosition = o3djs.math.matrix4.transformPoint(
              worldMatrix, rayIntersectionInfo.position);
          return o3djs.picking.createPickInfo(element,
                                              this,
                                              rayIntersectionInfo,
                                              worldIntersectionPosition);
        }
      }
    }
  }
  return null;
};

/**
 * Dumps info about a ShapeInfo
 * @param {string} opt_prefix optional prefix for indenting.
 */
o3djs.picking.ShapeInfo.prototype.dump = function(opt_prefix) {
  var prefix = opt_prefix || '';
  o3djs.picking.dprint(prefix + 'SHAPE: ' + this.shape.name + '\n');
  o3djs.picking.dprintPoint3('bb min',
                             this.boundingBox.minExtent,
                             prefix + '    ');
  o3djs.picking.dprintPoint3('bb max',
                             this.boundingBox.maxExtent,
                             prefix + '    ');
};

/**
 * Creates a new TransformInfo. Used to store information about Transforms.
 * @constructor
 * @param {!o3d.Transform} transform Transform to keep info about.
 * @param {o3djs.picking.TransformInfo} parent Parent transformInfo of the
 *     transform. Can be null.
 * @param {!o3djs.picking.PickManager} pickManager The PickManager this
 *     ShapeInfo belongs to.
 */
o3djs.picking.TransformInfo = function(transform, parent, pickManager) {
  /**
   * TransformInfos for children by client id.
   * @type {!Object.<number, !o3djs.picking.TransformInfo>}
   */
  this.childTransformInfos = {};

  /**
   * ShapeInfos for shape of this transform by client id.
   * @type {!Object.<number, !o3djs.picking.ShapeInfo>}
   */
  this.shapeInfos = {};

  /**
   * The transform of this transform info.
   * @type {!o3d.Transform}
   */
  this.transform = transform;

  /**
   * The parent of this transform info.
   * @type {o3djs.picking.TransformInfo}
   */
  this.parent = parent;

  /**
   * The bounding box of this transform info.
   * @type {o3d.BoundingBox}
   */
  this.boundingBox = null;

  /**
   * The PickManager this TransformInfo belongs to.
   * @type {!o3djs.picking.PickManager}
   */
  this.pickManager = pickManager;

  /**
   * Whether or not this object is pickable when invisible.
   * This is useful for collision geometry that is not visible.
   * Of course it might argubly be better to store collision geometry
   * in a separate graph from visible geometry but sometimes it's useful
   * to have them in the same graph.
   * @type {boolean}
   */
  this.pickableEvenIfInvisible = false;
};

/**
 * Gets the BoundingBox of the Transform in this TransformInfo.
 * @return {o3d.BoundingBox} The Transform's BoundingBox.
 */
o3djs.picking.TransformInfo.prototype.getBoundingBox = function() {
  return this.boundingBox;
};

/**
 * Returns whether or not this TransformInfo is pickable.
 *
 * If this TransformInfo is not pickable then all child shapes and
 * TransformInfos will be skipped during the picking process.
 *
 * @return {boolean} Whether or not this TransformInfo is pickable.
 */
o3djs.picking.TransformInfo.prototype.isPickable = function() {
  return this.transform.visible || this.pickableEvenIfInvisible;
};

/**
 * Updates the shape and child lists for this TransformInfo and recomputes its
 * BoundingBox.
 */
o3djs.picking.TransformInfo.prototype.update = function() {
  var newChildTransformInfos = {};
  var newShapeInfos = {};
  // We need to add new children and remove old ones so we walk the children
  // and for each one we find, if it already has a TransformInfo or ShapeInfo we
  // add it to our new lists, if not we create one and add it to our new lists.
  var children = this.transform.children;
  for (var c = 0; c < children.length; c++) {
    var child = children[c];
    var cid = child.clientId;
    var transformInfo = this.childTransformInfos[cid];
    if (!transformInfo) {
      transformInfo = this.pickManager.transformInfosByClientId[cid];
      if (!transformInfo) {
        transformInfo = this.pickManager.createTransformInfo(child, this);
      } else {
        // The Transform was reparented. Do the same for the TransformInfo.
        delete transformInfo.parent.childTransformInfos[cid];
        transformInfo.parent = this;
      }
    }
    // clear the boundingBox so we'll regenerate it.
    transformInfo.boundingBox = null;
    transformInfo.update();
    newChildTransformInfos[child.clientId] = transformInfo;
  }
  var shapes = this.transform.shapes;
  for (var s = 0; s < shapes.length; s++) {
    var shape = shapes[s];
    var shapeInfo = this.shapeInfos[shape.clientId];
    if (!shapeInfo) {
      shapeInfo = this.pickManager.createShapeInfo(shape, this);
    } else {
      // unless the vertices or elements change there is no need to
      // recompute this.
      // shapeInfo.update();
    }
    newShapeInfos[shape.clientId] = shapeInfo;
  }

  // o3djs.picking.dprint(
  //   'num Children: ' + children.length + '\n');
  // o3djs.picking.dprint(
  //   'num Shapes: ' + shapes.length + '\n');

  // Now our new lists have the correct children so replace the old lists.
  // and remove any old children from the PickManager.
  for (var skey in this.childTransformInfos) {
    var key = /** @type {number} */ (skey);
    if (!newChildTransformInfos[key]) {
      var info = this.childTransformInfos[key];
      // Check if the Transform is actually completely off the pick tree or if
      // it was just reparented.
      if (!this.pickManager.onPickTree(info.transform)) {
        this.pickManager.removeTransformInfo(info);
      }
    }
  }

  this.childTransformInfos = newChildTransformInfos;
  this.shapeInfos = newShapeInfos;

  var boundingBox = null;
  for (var key in newShapeInfos) {
    var shapeInfo = newShapeInfos[key];
    if (shapeInfo.isPickable()) {
      var box = shapeInfo.getBoundingBox().mul(this.transform.localMatrix);
      if (!boundingBox) {
        boundingBox = box;
      } else if (box) {
        boundingBox = boundingBox.add(box);
      }
    }
  }

  for (var key in newChildTransformInfos) {
    var transformInfo = newChildTransformInfos[key];
    if (transformInfo.isPickable()) {
      // Note: If there is no shape at the leaf on this branch
      // there will be no bounding box.
      var box = transformInfo.getBoundingBox();
      if (box) {
        if (!boundingBox) {
          boundingBox = box.mul(this.transform.localMatrix);
        } else {
          boundingBox = boundingBox.add(box.mul(this.transform.localMatrix));
        }
      }
    }
  }

  this.boundingBox = boundingBox;
};

/**
 * Attempts to "pick" this TransformInfo by checking for the intersection of a
 * ray in world space to the boundingbox of the TransformInfo. If intesection
 * is succesful recursively calls its children and shapes to try to find
 * a single Shape that is hit by the ray.
 * @param {!o3djs.picking.Ray} worldRay A ray in world space to pick against.
 * @return {o3djs.picking.PickInfo} Information about the picking.
 *     null if the ray did not intersect any triangles.
 */
o3djs.picking.TransformInfo.prototype.pick = function(worldRay) {
  if (this.isPickable() && this.boundingBox) {
    var inverseWorldMatrix = o3djs.math.matrix4.identity();
    if (this.parent) {
      inverseWorldMatrix = o3djs.math.inverse(
          this.parent.transform.getUpdatedWorldMatrix());
    }
    var relativeNear =
        o3djs.math.matrix4.transformPoint(inverseWorldMatrix, worldRay.near);
    var relativeFar =
        o3djs.math.matrix4.transformPoint(inverseWorldMatrix, worldRay.far);
    var rayIntersectionInfo =
        this.boundingBox.intersectRay(relativeNear, relativeFar);
    o3djs.picking.dumpRayIntersectionInfo(
        'TRANSFORM(box): ' + this.transform.name, rayIntersectionInfo);

    if (rayIntersectionInfo.intersected) {
      var closestPickInfo = null;
      var minDistance = -1;
      for (var skey in this.childTransformInfos) {
        var key = /** @type {number} */ (skey);
        var transformInfo = this.childTransformInfos[key];
        var pickInfo = transformInfo.pick(worldRay);
        if (pickInfo) {
          // is this closer than the last one?
          var distance = o3djs.math.lengthSquared(
              o3djs.math.subVector(worldRay.near,
                                   pickInfo.worldIntersectionPosition));
          if (!closestPickInfo || distance < minDistance) {
            minDistance = distance;
            closestPickInfo = pickInfo;
          }
        }
      }

      for (var skey in this.shapeInfos) {
        var key = /** @type {number} */ (skey);
        var shapeInfo = this.shapeInfos[key];
        var pickInfo = shapeInfo.pick(worldRay);
        if (pickInfo) {
          // is this closer than the last one?
          var distance = o3djs.math.lengthSquared(
              o3djs.math.subVector(worldRay.near,
                                   pickInfo.worldIntersectionPosition));
          if (!closestPickInfo || distance < minDistance) {
            minDistance = distance;
            closestPickInfo = pickInfo;
          }
        }
      }
      return closestPickInfo;
    }
  }
  return null;
};

/**
 * Dumps info about a TransformInfo
 * @param {string} opt_prefix optional prefix for indenting.
 */
o3djs.picking.TransformInfo.prototype.dump = function(opt_prefix) {
  var prefix = opt_prefix || '';

  o3djs.picking.dprint(prefix + 'TRANSFORM: ' + this.transform.name +
                       '\n');

  if (this.boundingBox) {
    o3djs.picking.dprintPoint3('bb min',
                               this.boundingBox.minExtent,
                               prefix + '    ');
    o3djs.picking.dprintPoint3('bb max',
                               this.boundingBox.maxExtent,
                               prefix + '    ');
  } else {
    o3djs.picking.dprint(prefix + '    bb *NA*\n');
  }

  o3djs.picking.dprint(prefix + '--Shapes--\n');
  for (var skey in this.shapeInfos) {
    var key = /** @type {number} */ (skey);
    var shapeInfo = this.shapeInfos[key];
    shapeInfo.dump(prefix + '    ');
  }

  o3djs.picking.dprint(prefix + '--Children--\n');
  for (var skey in this.childTransformInfos) {
    var key = /** @type {number} */ (skey);
    var transformInfo = this.childTransformInfos[key];
    transformInfo.dump(prefix + '    ');
  }
};

/**
 * A PickManager manages picking of primitives from a transform graph.
 * @constructor
 * @param {!o3d.Transform} rootTransform The root of the transform graph this
 *     PickManager should manage.
 */
o3djs.picking.PickManager = function(rootTransform) {
  /**
   * TransformInfos for transforms of this PickManager by client id.
   * @type {!Object.<number, !o3djs.picking.TransformInfo>}
   */
  this.transformInfosByClientId = {};

  /**
   * The root transform for this PickManager.
   * @type {!o3djs.picking.TransformInfo}
   */
  this.rootTransformInfo = this.createTransformInfo(rootTransform, null);
};

/**
 * Creates a new ShapeInfo.
 * @param {!o3d.Shape} shape Shape to keep info about.
 * @param {!o3djs.picking.TransformInfo} parent Parent transform of the shape.
 * @return {!o3djs.picking.ShapeInfo} The new ShapeInfo.
 */
o3djs.picking.PickManager.prototype.createShapeInfo = function(shape, parent) {
  return new o3djs.picking.ShapeInfo(shape, parent, this);
};

/**
 * Creates a new TransformInfo.
 * @param {!o3d.Transform} transform Transform to keep info about.
 * @param {o3djs.picking.TransformInfo} parent Parent transform of the
 *     transform. Can be null.
 * @return {!o3djs.picking.TransformInfo} The new TransformInfo.
 */
o3djs.picking.PickManager.prototype.createTransformInfo =
    function(transform, parent) {
  var info = new o3djs.picking.TransformInfo(transform, parent, this);
  this.addTransformInfo(info);
  return info;
};

/**
 * Adds a transform info to this PickManager.
 * @param {!o3djs.picking.TransformInfo} transformInfo The TransformInfo to add.
 */
o3djs.picking.PickManager.prototype.addTransformInfo = function(transformInfo) {
  this.transformInfosByClientId[transformInfo.transform.clientId] =
      transformInfo;
};

/**
 * Removes a transform info from this PickManager.
 * @param {!o3djs.picking.TransformInfo} transformInfo The TransformInfo to
 *     remove.
 */
o3djs.picking.PickManager.prototype.removeTransformInfo =
    function(transformInfo) {
  delete this.transformInfosByClientId[transformInfo.transform.clientId];
};

/**
 * Gets a transform info from this PickManager by transform.
 * @param {!o3d.Transform} transform The Transform to get a TransformInfo for.
 * @return {o3djs.picking.TransformInfo} The TransformInfo for the transform or
 *      null if there isn't one.
 */
o3djs.picking.PickManager.prototype.getTransformInfo = function(transform) {
  return this.transformInfosByClientId[transform.clientId];
};

/**
 * Updates the picking info to match the transform graph in its current state.
 */
o3djs.picking.PickManager.prototype.update = function() {
  this.rootTransformInfo.update();
};

/**
 * Dumps info about a PickManager
 */
o3djs.picking.PickManager.prototype.dump = function() {
  this.rootTransformInfo.dump();
};

/**
 * Attempts to "pick" objects managed by this PickManager.
 * @param {!o3djs.picking.Ray} worldRay A ray in world space to pick against.
 * @return {o3djs.picking.PickInfo} Information about the picking.
 *     null if the ray did not intersect any triangles.
 */
o3djs.picking.PickManager.prototype.pick = function(worldRay) {
  return this.rootTransformInfo.pick(worldRay);
};

/**
 * Creates a PickManager.
 * @param {!o3d.Transform} rootTransform The root of the transform graph this
 *     PickManager should manage.
 * @return {!o3djs.picking.PickManager} The created PickManager.
 */
o3djs.picking.createPickManager = function(rootTransform) {
  return new o3djs.picking.PickManager(rootTransform);
};

/**
 * Check to see if the given Transform lies on the pick tree.
 * @param {!o3d.Transform} transform the Transform to check
 * @return {boolean} true if the transform is a descendant of the pick root
 */
o3djs.picking.PickManager.prototype.onPickTree = function(transform) {
  var onTree = false;
  var parent = transform.parent;
  if (parent === this.rootTransformInfo.transform) {
    onTree = true;
  } else if (parent != null) {
    onTree = this.onPickTree(parent);
  }
  return onTree;
};

/*
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview This file contains functions to create geometric primitives for
 * o3d.  It puts them in the "primitives" module on the o3djs object.
 *
 * For more information about o3d see http://code.google.com/p/o3d
 *
 *
 * Requires base.js
 */

o3djs.provide('o3djs.primitives');

/**
 * A Module for creating primitives.
 * @namespace
 */
o3djs.primitives = o3djs.primitives || {};


/**
 * Sets the bounding box and zSortPoint for a primitive based on its vertices
 *
 * @param {!o3d.Primitive} primitive Primitive to set culling info for.
 */
o3djs.primitives.setCullingInfo = function(primitive) {
  var box = primitive.getBoundingBox(0);
  primitive.boundingBox = box;
  var minExtent = box.minExtent;
  var maxExtent = box.maxExtent;
  primitive.zSortPoint = o3djs.math.divVectorScalar(
      o3djs.math.addVector(minExtent, maxExtent), 2);
};

/**
 * Used to store the elements of a stream.
 * @param {number} numComponents The number of numerical components per
 *     element.
 * @param {!o3d.Stream.Semantic} semantic The semantic of the stream.
 * @param {number} opt_semanticIndex The semantic index of the stream.
 *     Defaults to zero.
 * @constructor
 */
o3djs.primitives.VertexStreamInfo = function(numComponents,
                                             semantic,
                                             opt_semanticIndex) {
  /**
   * The number of numerical components per element.
   * @type {number}
   */
  this.numComponents = numComponents;

  /**
   * The semantic of the stream.
   * @type {!o3d.Stream.Semantic}
   */
  this.semantic = semantic;

  /**
   * The semantic index of the stream.
   * @type {number}
   */
  this.semanticIndex = opt_semanticIndex || 0;

  /**
   * The elements of the stream.
   * @type {!Array.<number>}
   */
  this.elements = [];

  /**
   * Adds an element to this VertexStreamInfo. The number of values passed must
   * match the number of components for this VertexStreamInfo.
   * @param {number} value1 First value.
   * @param {number} opt_value2 Second value.
   * @param {number} opt_value3 Third value.
   * @param {number} opt_value4 Fourth value.
   */
  this.addElement = function(value1, opt_value2, opt_value3, opt_value4) { };

  /**
   * Sets an element on this VertexStreamInfo. The number of values passed must
   * match the number of components for this VertexStreamInfo.
   * @param {number} index Index of element to set.
   * @param {number} value1 First value.
   * @param {number} opt_value2 Second value.
   * @param {number} opt_value3 Third value.
   * @param {number} opt_value4 Fourth value.
   */
  this.setElement = function(
      index, value1, opt_value2, opt_value3, opt_value4) { };

  /**
   * Adds an element to this VertexStreamInfo. The number of values in the
   * vector must match the number of components for this VertexStreamInfo.
   * @param {!Array.<number>} vector Array of values for element.
   */
  this.addElementVector = function(vector) { };  // replaced below.

  /**
   * Sets an element on this VertexStreamInfo. The number of values in the
   * vector must match the number of components for this VertexStreamInfo.
   * @param {number} index Index of element to set.
   * @param {!Array.<number>} vector Array of values for element.
   */
  this.setElementVector = function(index, vector) { };  // replaced below.

  /**
   * Sets an element on this VertexStreamInfo. The number of values in the
   * vector will match the number of components for this VertexStreamInfo.
   * @param {number} index Index of element to set.
   * @return {!Array.<number>} Array of values for element.
   */
  this.getElementVector = function(index) { return []; };  // replaced below.

  switch (numComponents) {
    case 1:
      this.addElement = function(value) {
        this.elements.push(value);
      }
      this.getElement = function(index) {
        return this.elements[index];
      }
      this.setElement = function(index, value) {
        this.elements[index] = value;
      }
      break;
    case 2:
      this.addElement = function(value0, value1) {
        this.elements.push(value0, value1);
      }
      this.addElementVector = function(vector) {
        this.elements.push(vector[0], vector[1]);
      }
      this.getElementVector = function(index) {
        return this.elements.slice(index * numComponents,
                                   (index + 1) * numComponents);
      }
      this.setElement = function(index, value0, value1) {
        this.elements[index * numComponents + 0] = value0;
        this.elements[index * numComponents + 1] = value1;
      }
      this.setElementVector = function(index, vector) {
        this.elements[index * numComponents + 0] = vector[0];
        this.elements[index * numComponents + 1] = vector[1];
      }
      break;
    case 3:
      this.addElement = function(value0, value1, value2) {
        this.elements.push(value0, value1, value2);
      }
      this.addElementVector = function(vector) {
        this.elements.push(vector[0], vector[1], vector[2]);
      }
      this.getElementVector = function(index) {
        return this.elements.slice(index * numComponents,
                                   (index + 1) * numComponents);
      }
      this.setElement = function(index, value0, value1, value2) {
        this.elements[index * numComponents + 0] = value0;
        this.elements[index * numComponents + 1] = value1;
        this.elements[index * numComponents + 2] = value2;
      }
      this.setElementVector = function(index, vector) {
        this.elements[index * numComponents + 0] = vector[0];
        this.elements[index * numComponents + 1] = vector[1];
        this.elements[index * numComponents + 2] = vector[2];
      }
      break;
    case 4:
      this.addElement = function(value0, value1, value2, value3) {
        this.elements.push(value0, value1, value2, value3);
      }
      this.addElementVector = function(vector) {
        this.elements.push(vector[0], vector[1], vector[2], vector[3]);
      }
      this.getElementVector = function(index) {
        return this.elements.slice(index * numComponents,
                                   (index + 1) * numComponents);
      }
      this.setElement = function(index, value0, value1, value2, value3) {
        this.elements[index * numComponents + 0] = value0;
        this.elements[index * numComponents + 1] = value1;
        this.elements[index * numComponents + 2] = value2;
        this.elements[index * numComponents + 3] = value3;
      }
      this.setElementVector = function(index, vector) {
        this.elements[index * numComponents + 0] = vector[0];
        this.elements[index * numComponents + 1] = vector[1];
        this.elements[index * numComponents + 2] = vector[2];
        this.elements[index * numComponents + 3] = vector[3];
      }
      break;
    default:
      throw 'A stream must contain between 1 and 4 components';
  }
};

/**
 * Get the number of elements in the stream.
 * @return {number} The number of elements in the stream.
 */
o3djs.primitives.VertexStreamInfo.prototype.numElements = function() {
  return this.elements.length / this.numComponents;
};

/**
 * Create a VertexStreamInfo.
 * @param {number} numComponents The number of numerical components per
 *     element.
 * @param {!o3d.Stream.Semantic} semantic The semantic of the stream.
 * @param {number} opt_semanticIndex The semantic index of the stream.
 *     Defaults to zero.
 * @return {!o3djs.primitives.VertexStreamInfo} The new stream.
 */
o3djs.primitives.createVertexStreamInfo = function(numComponents,
                                                   semantic,
                                                   opt_semanticIndex) {
  return new o3djs.primitives.VertexStreamInfo(numComponents,
                                               semantic,
                                               opt_semanticIndex);
};

/**
 * VertexInfoBase. Used to store vertices and indices.
 * @constructor
 */
o3djs.primitives.VertexInfoBase = function() {
  this.streams = [];
  this.indices = [];
};

/**
 * Add a new stream to the VertexInfo, replacing it with a new empty one
 *     if it already exists.
 * @param {number} numComponents The number of components per vector.
 * @param {!o3d.Stream.Semantic} semantic The semantic of the stream.
 * @param {number} opt_semanticIndex The semantic index of the stream.
 *     Defaults to zero.
 * @return {!o3djs.primitives.VertexStreamInfo} The new stream.
 */
o3djs.primitives.VertexInfoBase.prototype.addStream = function(
    numComponents,
    semantic,
    opt_semanticIndex) {
  this.removeStream(semantic, opt_semanticIndex);
  var stream = o3djs.primitives.createVertexStreamInfo(
      numComponents,
      semantic,
      opt_semanticIndex);
  this.streams.push(stream);
  return stream;
};

/**
 * Find a stream in the VertexInfo.
 * @param {!o3d.Stream.Semantic} semantic The semantic of the stream.
 * @param {number} opt_semanticIndex The semantic index of the stream.
 *     Defaults to zero.
 * @return {o3djs.primitives.VertexStreamInfo} The stream or null if it
 *     is not present.
 */
o3djs.primitives.VertexInfoBase.prototype.findStream = function(
    semantic,
    opt_semanticIndex) {
  opt_semanticIndex = opt_semanticIndex || 0;
  for (var i = 0; i < this.streams.length; ++i) {
    if (this.streams[i].semantic === semantic &&
        this.streams[i].semanticIndex == opt_semanticIndex) {
      return this.streams[i];
    }
  }
  return null;
};

/**
 * Remove a stream from the VertexInfo. Does nothing if a matching stream
 * does not exist.
 * @param {!o3d.Stream.Semantic} semantic The semantic of the stream.
 * @param {number} opt_semanticIndex The semantic index of the stream.
 *     Defaults to zero.
 */
o3djs.primitives.VertexInfoBase.prototype.removeStream = function(
    semantic,
    opt_semanticIndex) {
  opt_semanticIndex = opt_semanticIndex || 0;
  for (var i = 0; i < this.streams.length; ++i) {
    if (this.streams[i].semantic === semantic &&
        this.streams[i].semanticIndex == opt_semanticIndex) {
      this.streams.splice(i, 1);
      return;
    }
  }
};

/**
 * Appends all of the information in the passed VertexInfo on to the
 * end of this one. This is useful for putting multiple primitives'
 * vertices, appropriately transformed, into a single Shape. Both
 * VertexInfo objects must contain the same number of streams, with
 * the same semantics and number of components.
 * @param {!o3djs.primitives.VertexInfoBase} info The VertexInfo whose
 *     information should be appended to this one.
 */
o3djs.primitives.VertexInfoBase.prototype.append = function(info) {
  if (this.streams.length == 0 && info.streams.length != 0) {
    // Special case
    for (var i = 0; i < info.streams.length; i++) {
      var srcStream = info.streams[i];
      var stream = this.addStream(srcStream.numComponents,
                                  srcStream.semantic,
                                  srcStream.semanticIndex);
      stream.elements = stream.elements.concat(srcStream.elements);
    }
    this.indices = this.indices.concat(info.indices);
    return;
  }

  // First verify that both have the same streams
  if (this.streams.length != info.streams.length) {
    throw 'Number of VertexInfoStreams did not match';
  }
  for (var i = 0; i < this.streams.length; i++) {
    var found = false;
    var semantic = this.streams[i].semantic;
    var numComponents = this.streams[i].numComponents;
    var semanticIndex = this.streams[i].semanticIndex;
    for (var j = 0; j < info.streams.length; j++) {
      var otherStream = info.streams[j];
      if (otherStream.semantic === semantic &&
          otherStream.numComponents == numComponents &&
          otherStream.semanticIndex == semanticIndex) {
        found = true;
        break;
      }
    }
    if (!found) {
      throw 'Did not find stream with semantic=' + semantic +
        ', numComponents=' + numComponents +
        ', and semantic index=' + semanticIndex +
        ' in given VertexInfo';
    }
  }

  // Compute the number of vertices currently in the shape
  var positionStream = this.findStream(o3djs.base.o3d.Stream.POSITION);
  if (!positionStream)
    throw 'POSITION stream is missing';
  var numVertices = positionStream.numElements();

  // Concatenate all VertexStreamInfos' data
  for (var i = 0; i < this.streams.length; i++) {
    var stream = this.streams[i];
    var srcStream = info.findStream(stream.semantic, stream.semanticIndex);
    stream.elements = stream.elements.concat(srcStream.elements);
  }

  // Concatenate and adjust indices
  for (var i = 0; i < info.indices.length; i++) {
    this.indices.push(info.indices[i] + numVertices);
  }
};

/**
 * Validates that all the streams contain the same number of elements, that
 * all the indices are within range and that a position stream is present.
 */
o3djs.primitives.VertexInfoBase.prototype.validate = function() {
  // Check the position stream is present.
  var positionStream = this.findStream(o3djs.base.o3d.Stream.POSITION);
  if (!positionStream)
    throw 'POSITION stream is missing';

  // Check all the streams have the same number of elements.
  var numElements = positionStream.numElements();
  for (var s = 0; s < this.streams.length; ++s) {
    if (this.streams[s].numElements() !== numElements) {
      throw 'Stream ' + s + ' contains ' + this.streams[s].numElements() +
          ' elements whereas the POSITION stream contains ' + numElements;
    }
  }

  // Check all the indices are in range.
  for (var i = 0; i < this.indices.length; ++i) {
    if (this.indices[i] < 0 || this.indices[i] >= numElements) {
      throw 'The index ' + this.indices[i] + ' is out of range [0, ' +
        numElements + ']';
    }
  }
};

/**
 * Reorients the vertices, positions and normals, of this vertexInfo by the
 * given matrix. In other words, it multiplies each vertex by the given matrix
 * and each normal by the inverse-transpose of the given matrix.
 * @param {!o3djs.math.Matrix4} matrix Matrix by which to multiply.
 */
o3djs.primitives.VertexInfoBase.prototype.reorient = function(matrix) {
  var math = o3djs.math;
  var matrixInverse = math.inverse(math.matrix4.getUpper3x3(matrix));

  for (var s = 0; s < this.streams.length; ++s) {
    var stream = this.streams[s];
    if (stream.numComponents == 3) {
      var numElements = stream.numElements();
      switch (stream.semantic) {
        case o3djs.base.o3d.Stream.POSITION:
          for (var i = 0; i < numElements; ++i) {
            stream.setElementVector(i,
                math.matrix4.transformPoint(matrix,
                    stream.getElementVector(i)));
          }
          break;
        case o3djs.base.o3d.Stream.NORMAL:
          for (var i = 0; i < numElements; ++i) {
            stream.setElementVector(i,
                math.matrix4.transformNormal(matrix,
                    stream.getElementVector(i)));
          }
          break;
        case o3djs.base.o3d.Stream.TANGENT:
        case o3djs.base.o3d.Stream.BINORMAL:
          for (var i = 0; i < numElements; ++i) {
            stream.setElementVector(i,
                math.matrix4.transformDirection(matrix,
                    stream.getElementVector(i)));
          }
          break;
      }
    }
  }
};

/**
 * Creates a shape from a VertexInfoBase
 * @param {!o3d.Pack} pack Pack to create objects in.
 * @param {!o3d.Material} material to use.
 * @param {!o3d.Primitive.PrimitiveType} primitiveType The type of primitive.
 * @return {!o3d.Shape} The created shape.
 */
o3djs.primitives.VertexInfoBase.prototype.createShapeByType = function(
    pack,
    material,
    primitiveType) {
  this.validate();

  var numIndices = this.indices.length;
  var numPrimitives;
  switch (primitiveType) {
    case o3djs.base.o3d.Primitive.POINTLIST:
      numPrimitives = numIndices / 1;
      break;
    case o3djs.base.o3d.Primitive.LINELIST:
      numPrimitives = numIndices / 2;
      break;
    case o3djs.base.o3d.Primitive.LINESTRIP:
      numPrimitives = numIndices - 1;
      break;
    case o3djs.base.o3d.Primitive.TRIANGLELIST:
      numPrimitives = numIndices / 3;
      break;
    case o3djs.base.o3d.Primitive.TRIANGLESTRIP:
    case o3djs.base.o3d.Primitive.TRIANGLEFAN:
      numPrimitives = numIndices - 2;
      break;
    default:
      throw 'unknown primitive type';
  }

  var positionStream = this.findStream(o3djs.base.o3d.Stream.POSITION);
  var numVertices = positionStream.numElements();

  // create a shape and primitive for the vertices.
  var shape = pack.createObject('Shape');
  var primitive = pack.createObject('Primitive');
  var streamBank = pack.createObject('StreamBank');
  primitive.owner = shape;
  primitive.streamBank = streamBank;
  primitive.material = material;
  primitive.numberPrimitives = numPrimitives;
  primitive.primitiveType = primitiveType;
  primitive.numberVertices = numVertices;
  primitive.createDrawElement(pack, null);

  // Calculate the tangent and binormal or provide defaults or fail if the
  // effect requires either and they are not present.
  var streamInfos = material.effect.getStreamInfo();
  for (var s = 0; s < streamInfos.length; ++s) {
    var semantic = streamInfos[s].semantic;
    var semanticIndex = streamInfos[s].semanticIndex;

    var requiredStream = this.findStream(semantic, semanticIndex);
    if (!requiredStream) {
      switch (semantic) {
        case o3djs.base.o3d.Stream.TANGENT:
        case o3djs.base.o3d.Stream.BINORMAL:
          if (primitiveType == o3djs.base.o3d.Primitive.TRIANGLELIST) {
            this.addTangentStreams(semanticIndex);
          } else {
            throw 'Can not create tangents and binormals for primitive type' +
                primitiveType;
          }
          break;
        case o3djs.base.o3d.Stream.COLOR:
          requiredStream = this.addStream(4, semantic, semanticIndex);
          for (var i = 0; i < numVertices; ++i) {
            requiredStream.addElement(1, 1, 1, 1);
          }
          break;
        case o3djs.base.o3d.Stream.INFLUENCE_WEIGHTS:
        case o3djs.base.o3d.Stream.INFLUENCE_INDICES:
          break;
        default:
          throw 'Missing stream for semantic ' + semantic +
              ' with semantic index ' + semanticIndex;
      }
    }
  }

  // These next few lines take our javascript streams and load them into a
  // 'buffer' where the 3D hardware can find them. We have to do this
  // because the 3D hardware can't 'see' javascript data until we copy it to
  // a buffer.
  var vertexBuffer = pack.createObject('VertexBuffer');
  var fields = [];
  for (var s = 0; s < this.streams.length; ++s) {
    var stream = this.streams[s];
    var fieldType = (stream.semantic == o3djs.base.o3d.Stream.COLOR &&
                     stream.numComponents == 4) ? 'UByteNField' : 'FloatField';
    fields[s] = vertexBuffer.createField(fieldType, stream.numComponents);
    streamBank.setVertexStream(stream.semantic,
                               stream.semanticIndex,
                               fields[s],
                               0);
  }
  vertexBuffer.allocateElements(numVertices);
  for (var s = 0; s < this.streams.length; ++s) {
    fields[s].setAt(0, this.streams[s].elements);
  }

  var indexBuffer = pack.createObject('IndexBuffer');
  indexBuffer.set(this.indices);
  primitive.indexBuffer = indexBuffer;
  o3djs.primitives.setCullingInfo(primitive);
  return shape;
};

/**
 * A VertexInfo is a specialization of VertexInfoBase for triangle based
 * geometry.
 * @constructor
 * @extends {o3djs.primitives.VertexInfoBase}
 */
o3djs.primitives.VertexInfo = function() {
  o3djs.primitives.VertexInfoBase.call(this);
}

o3djs.base.inherit(o3djs.primitives.VertexInfo,
                   o3djs.primitives.VertexInfoBase);

/**
 * Returns the number of triangles represented by the VertexInfo.
 * @return {number} The number of triangles represented by VertexInfo.
 */
o3djs.primitives.VertexInfo.prototype.numTriangles = function() {
  return this.indices.length / 3;
};

/**
 * Adds a triangle.
 * @param {number} index1 The index of the first vertex of the triangle.
 * @param {number} index2 The index of the second vertex of the triangle.
 * @param {number} index3 The index of the third vertex of the triangle.
 */
o3djs.primitives.VertexInfo.prototype.addTriangle = function(
    index1, index2, index3) {
  this.indices.push(index1, index2, index3);
};

/**
 * Gets the vertex indices of the triangle at the given triangle index.
 * @param {number} triangleIndex The index of the triangle.
 * @return {!Array.<number>} An array of three triangle indices.
 */
o3djs.primitives.VertexInfo.prototype.getTriangle = function(
    triangleIndex) {
  var indexIndex = triangleIndex * 3;
  return [this.indices[indexIndex + 0],
          this.indices[indexIndex + 1],
          this.indices[indexIndex + 2]];
};

/**
 * Sets the vertex indices of the triangle at the given triangle index.
 * @param {number} triangleIndex The index of the triangle.
 * @param {number} index1 The index of the first vertex of the triangle.
 * @param {number} index2 The index of the second vertex of the triangle.
 * @param {number} index3 The index of the third vertex of the triangle.
 */
o3djs.primitives.VertexInfo.prototype.setTriangle = function(
    triangleIndex, index1, index2, index3) {
  var indexIndex = triangleIndex * 3;
  this.indices[indexIndex + 0] = index1;
  this.indices[indexIndex + 1] = index2;
  this.indices[indexIndex + 2] = index3;
};

/**
 * Creates a shape from a VertexInfo
 * @param {!o3d.Pack} pack Pack to create objects in.
 * @param {!o3d.Material} material to use.
 * @return {!o3d.Shape} The created shape.
 */
o3djs.primitives.VertexInfo.prototype.createShape = function(
    pack,
    material) {
  return this.createShapeByType(
      pack, material, o3djs.base.o3d.Primitive.TRIANGLELIST);
};

/**
 * Calculate tangents and binormals based on the positions, normals and
 *     texture coordinates found in existing streams.
 * @param {number} opt_semanticIndex The semantic index of the texture
 *     coordinate to use and the tangent and binormal streams to add. Defaults
 *     to zero.
 */
o3djs.primitives.VertexInfo.prototype.addTangentStreams =
    function(opt_semanticIndex) {
  opt_semanticIndex = opt_semanticIndex || 0;
  var math = o3djs.math;

  this.validate();

  // Find and validate the position, normal and texture coordinate frames.
  var positionStream = this.findStream(o3djs.base.o3d.Stream.POSITION);
  if (!positionStream)
    throw 'Cannot calculate tangent frame because POSITION stream is missing';
  if (positionStream.numComponents != 3)
    throw 'Cannot calculate tangent frame because POSITION stream is not 3D';

  var normalStream = this.findStream(o3djs.base.o3d.Stream.NORMAL);
  if (!normalStream)
    throw 'Cannot calculate tangent frame because NORMAL stream is missing';
  if (normalStream.numComponents != 3)
    throw 'Cannot calculate tangent frame because NORMAL stream is not 3D';

  var texCoordStream = this.findStream(o3djs.base.o3d.Stream.TEXCOORD,
                                       opt_semanticIndex);
  if (!texCoordStream)
    throw 'Cannot calculate tangent frame because TEXCOORD stream ' +
        opt_semanticIndex + ' is missing';

  // Maps from position, normal key to tangent and binormal matrix.
  var tangentFrames = {};

  // Rounds a vector to integer components.
  function roundVector(v) {
    return [Math.round(v[0]), Math.round(v[1]), Math.round(v[2])];
  }

  // Generates a key for the tangentFrames map from a position and normal
  // vector. Rounds position and normal to allow some tolerance.
  function tangentFrameKey(position, normal) {
    return roundVector(math.mulVectorScalar(position, 100)) + ',' +
        roundVector(math.mulVectorScalar(normal, 100));
  }

  // Accumulates into the tangent and binormal matrix at the approximate
  // position and normal.
  function addTangentFrame(position, normal, tangent, binormal) {
    var key = tangentFrameKey(position, normal);
    var frame = tangentFrames[key];
    if (!frame) {
      frame = [[0, 0, 0], [0, 0, 0]];
    }
    frame = math.addMatrix(frame, [tangent, binormal]);
    tangentFrames[key] = frame;
  }

  // Get the tangent and binormal matrix at the approximate position and
  // normal.
  function getTangentFrame(position, normal) {
    var key = tangentFrameKey(position, normal);
    return tangentFrames[key];
  }

  var numTriangles = this.numTriangles();
  for (var triangleIndex = 0; triangleIndex < numTriangles; ++triangleIndex) {
    // Get the vertex indices, uvs and positions for the triangle.
    var vertexIndices = this.getTriangle(triangleIndex);
    var uvs = [];
    var positions = [];
    var normals = [];
    for (var i = 0; i < 3; ++i) {
      var vertexIndex = vertexIndices[i];
      uvs[i] = texCoordStream.getElementVector(vertexIndex);
      positions[i] = positionStream.getElementVector(vertexIndex);
      normals[i] = normalStream.getElementVector(vertexIndex);
    }

    // Calculate the tangent and binormal for the triangle using method
    // described in Maya documentation appendix A: tangent and binormal
    // vectors.
    var tangent = [0, 0, 0];
    var binormal = [0, 0, 0];
    for (var axis = 0; axis < 3; ++axis) {
      var edge1 = [positions[1][axis] - positions[0][axis],
                   uvs[1][0] - uvs[0][0], uvs[1][1] - uvs[0][1]];
      var edge2 = [positions[2][axis] - positions[0][axis],
                   uvs[2][0] - uvs[0][0], uvs[2][1] - uvs[0][1]];
      var edgeCross = math.normalize(math.cross(edge1, edge2));
      if (edgeCross[0] == 0) {
        edgeCross[0] = 1;
      }
      tangent[axis] = -edgeCross[1] / edgeCross[0];
      binormal[axis] = -edgeCross[2] / edgeCross[0];
    }

    // Normalize the tangent and binornmal.
    var tangentLength = math.length(tangent);
    if (tangentLength > 0.001) {
      tangent = math.mulVectorScalar(tangent, 1 / tangentLength);
    }
    var binormalLength = math.length(binormal);
    if (binormalLength > 0.001) {
      binormal = math.mulVectorScalar(binormal, 1 / binormalLength);
    }

    // Accumulate the tangent and binormal into the tangent frame map.
    for (var i = 0; i < 3; ++i) {
      addTangentFrame(positions[i], normals[i], tangent, binormal);
    }
  }

  // Add the tangent and binormal streams.
  var tangentStream = this.addStream(3,
                                     o3djs.base.o3d.Stream.TANGENT,
                                     opt_semanticIndex);
  var binormalStream = this.addStream(3,
                                      o3djs.base.o3d.Stream.BINORMAL,
                                      opt_semanticIndex);

  // Extract the tangent and binormal for each vertex.
  var numVertices = positionStream.numElements();
  for (var vertexIndex = 0; vertexIndex < numVertices; ++vertexIndex) {
    var position = positionStream.getElementVector(vertexIndex);
    var normal = normalStream.getElementVector(vertexIndex);
    var frame = getTangentFrame(position, normal);

    // Orthonormalize the tangent with respect to the normal.
    var tangent = frame[0];
    tangent = math.subVector(
        tangent, math.mulVectorScalar(normal, math.dot(normal, tangent)));
    var tangentLength = math.length(tangent);
    if (tangentLength > 0.001) {
      tangent = math.mulVectorScalar(tangent, 1 / tangentLength);
    }

    // Orthonormalize the binormal with respect to the normal and the tangent.
    var binormal = frame[1];
    binormal = math.subVector(
        binormal, math.mulVectorScalar(tangent, math.dot(tangent, binormal)));
    binormal = math.subVector(
        binormal, math.mulVectorScalar(normal, math.dot(normal, binormal)));
    var binormalLength = math.length(binormal);
    if (binormalLength > 0.001) {
      binormal = math.mulVectorScalar(binormal, 1 / binormalLength);
    }

    tangentStream.setElementVector(vertexIndex, tangent);
    binormalStream.setElementVector(vertexIndex, binormal);
  }
};

/**
 * Creates a new VertexInfo.
 * @return {!o3djs.primitives.VertexInfo} The new VertexInfo.
 */
o3djs.primitives.createVertexInfo = function() {
  return new o3djs.primitives.VertexInfo();
};

/**
 * Creates sphere vertices.
 * The created sphere has position, normal and uv streams.
 *
 * @param {number} radius radius of the sphere.
 * @param {number} subdivisionsAxis number of steps around the sphere.
 * @param {number} subdivisionsHeight number of vertically on the sphere.
 * @param {!o3djs.math.Matrix4} opt_matrix A matrix by which to multiply
 *     all the vertices.
 * @return {!o3djs.primitives.VertexInfo} The created sphere vertices.
 */
o3djs.primitives.createSphereVertices = function(radius,
                                                 subdivisionsAxis,
                                                 subdivisionsHeight,
                                                 opt_matrix) {
  if (subdivisionsAxis <= 0 || subdivisionsHeight <= 0) {
    throw Error('subdivisionAxis and subdivisionHeight must be > 0');
  }

  // We are going to generate our sphere by iterating through its
  // spherical coordinates and generating 2 triangles for each quad on a
  // ring of the sphere.

  var vertexInfo = o3djs.primitives.createVertexInfo();
  var positionStream = vertexInfo.addStream(
      3, o3djs.base.o3d.Stream.POSITION);
  var normalStream = vertexInfo.addStream(
      3, o3djs.base.o3d.Stream.NORMAL);
  var texCoordStream = vertexInfo.addStream(
      2, o3djs.base.o3d.Stream.TEXCOORD, 0);

  // Generate the individual vertices in our vertex buffer.
  for (var y = 0; y <= subdivisionsHeight; y++) {
    for (var x = 0; x <= subdivisionsAxis; x++) {
      // Generate a vertex based on its spherical coordinates
      var u = x / subdivisionsAxis;
      var v = y / subdivisionsHeight;
      var theta = 2 * Math.PI * u;
      var phi = Math.PI * v;
      var sinTheta = Math.sin(theta);
      var cosTheta = Math.cos(theta);
      var sinPhi = Math.sin(phi);
      var cosPhi = Math.cos(phi);
      var ux = cosTheta * sinPhi;
      var uy = cosPhi;
      var uz = sinTheta * sinPhi;
      positionStream.addElement(radius * ux, radius * uy, radius * uz);
      normalStream.addElement(ux, uy, uz);
      texCoordStream.addElement(1 - u, 1 - v);
    }
  }
  var numVertsAround = subdivisionsAxis + 1;

  for (var x = 0; x < subdivisionsAxis; x++) {
    for (var y = 0; y < subdivisionsHeight; y++) {
      // Make triangle 1 of quad.
      vertexInfo.addTriangle(
          (y + 0) * numVertsAround + x,
          (y + 0) * numVertsAround + x + 1,
          (y + 1) * numVertsAround + x);

      // Make triangle 2 of quad.
      vertexInfo.addTriangle(
          (y + 1) * numVertsAround + x,
          (y + 0) * numVertsAround + x + 1,
          (y + 1) * numVertsAround + x + 1);
    }
  }

  if (opt_matrix) {
    vertexInfo.reorient(opt_matrix);
  }
  return vertexInfo;
};

/**
 * Creates a sphere.
 * The created sphere has position, normal and uv streams.
 *
 * @param {!o3d.Pack} pack Pack to create sphere elements in.
 * @param {!o3d.Material} material to use.
 * @param {number} radius radius of the sphere.
 * @param {number} subdivisionsAxis number of steps around the sphere.
 * @param {number} subdivisionsHeight number of vertically on the sphere.
 * @param {!o3djs.math.Matrix4} opt_matrix A matrix by which to multiply
 *     all the vertices.
 * @return {!o3d.Shape} The created sphere.
 *
 * @see o3d.Pack
 * @see o3d.Shape
 */
o3djs.primitives.createSphere = function(pack,
                                         material,
                                         radius,
                                         subdivisionsAxis,
                                         subdivisionsHeight,
                                         opt_matrix) {
  var vertexInfo = o3djs.primitives.createSphereVertices(
      radius,
      subdivisionsAxis,
      subdivisionsHeight,
      opt_matrix);

  return vertexInfo.createShape(pack, material);
};

/**
 * Array of the indices of corners of each face of a cube.
 * @private
 * @type {!Array.<!Array.<number>>}
 */
o3djs.primitives.CUBE_FACE_INDICES_ = [
  [3, 7, 5, 1],
  [0, 4, 6, 2],
  [6, 7, 3, 2],
  [0, 1, 5, 4],
  [5, 7, 6, 4],
  [2, 3, 1, 0]
];

/**
 * Creates the vertices and indices for a cube. The
 * cube will be created around the origin. (-size / 2, size / 2)
 * The created cube has position, normal and uv streams.
 *
 * @param {number} size Width, height and depth of the cube.
 * @param {!o3djs.math.Matrix4} opt_matrix A matrix by which to multiply
 *     all the vertices.
 * @return {!o3djs.primitives.VertexInfo} The created cube vertices.
 */
o3djs.primitives.createCubeVertices = function(size, opt_matrix) {
  var k = size / 2;

  var cornerVertices = [
    [-k, -k, -k],
    [+k, -k, -k],
    [-k, +k, -k],
    [+k, +k, -k],
    [-k, -k, +k],
    [+k, -k, +k],
    [-k, +k, +k],
    [+k, +k, +k]
  ];

  var faceNormals = [
    [+1, +0, +0],
    [-1, +0, +0],
    [+0, +1, +0],
    [+0, -1, +0],
    [+0, +0, +1],
    [+0, +0, -1]
  ];

  var uvCoords = [
    [0, 0],
    [1, 0],
    [1, 1],
    [0, 1]
  ];

  var vertexInfo = o3djs.primitives.createVertexInfo();
  var positionStream = vertexInfo.addStream(
      3, o3djs.base.o3d.Stream.POSITION);
  var normalStream = vertexInfo.addStream(
      3, o3djs.base.o3d.Stream.NORMAL);
  var texCoordStream = vertexInfo.addStream(
      2, o3djs.base.o3d.Stream.TEXCOORD, 0);

  for (var f = 0; f < 6; ++f) {
    var faceIndices = o3djs.primitives.CUBE_FACE_INDICES_[f];
    for (var v = 0; v < 4; ++v) {
      var position = cornerVertices[faceIndices[v]];
      var normal = faceNormals[f];
      var uv = uvCoords[v];

      // Each face needs all four vertices because the normals and texture
      // coordinates are not all the same.
      positionStream.addElementVector(position);
      normalStream.addElementVector(normal);
      texCoordStream.addElementVector(uv);

      // Two triangles make a square face.
      var offset = 4 * f;
      vertexInfo.addTriangle(offset + 0, offset + 1, offset + 2);
      vertexInfo.addTriangle(offset + 0, offset + 2, offset + 3);
    }
  }

  if (opt_matrix) {
    vertexInfo.reorient(opt_matrix);
  }
  return vertexInfo;
};

/**
 * Creates a cube.
 * The cube will be created around the origin. (-size / 2, size / 2)
 * The created cube has position, normal and uv streams.
 *
 * @param {!o3d.Pack} pack Pack to create cube elements in.
 * @param {!o3d.Material} material to use.
 * @param {number} size Width, height and depth of the cube.
 * @param {!o3djs.math.Matrix4} opt_matrix A matrix by which to multiply
 *     all the vertices.
 * @return {!o3d.Shape} The created cube.
 *
 * @see o3d.Pack
 * @see o3d.Shape
 */
o3djs.primitives.createCube = function(pack,
                                       material,
                                       size,
                                       opt_matrix) {
  var vertexInfo = o3djs.primitives.createCubeVertices(size, opt_matrix);
  return vertexInfo.createShape(pack, material);
};

/**
 * Creates a box. The box will be created around the origin.
 * The created box has position, normal and uv streams.
 *
 * @param {!o3d.Pack} pack Pack to create Box elements in.
 * @param {!o3d.Material} material to use.
 * @param {number} width Width of the box.
 * @param {number} height Height of the box.
 * @param {number} depth Depth of the box.
 * @param {!o3djs.math.Matrix4} opt_matrix A matrix by which to multiply
 *     all the vertices.
 * @return {!o3d.Shape} The created Box.
 *
 * @see o3d.Pack
 * @see o3d.Shape
 */
o3djs.primitives.createBox = function(pack,
                                      material,
                                      width,
                                      height,
                                      depth,
                                      opt_matrix) {
  var vertexInfo = o3djs.primitives.createCubeVertices(1);
  vertexInfo.reorient([[width, 0, 0, 0],
                       [0, height, 0, 0],
                       [0, 0, depth, 0],
                       [0, 0, 0, 1]]);

  if (opt_matrix) {
    vertexInfo.reorient(opt_matrix);
  }
  return vertexInfo.createShape(pack, material);
};

/**
 * Creates a cube with varying vertex colors. The cube will be created
 * around the origin. (-size / 2, size / 2)
 *
 * @param {!o3d.Pack} pack Pack to create cube elements in.
 * @param {!o3d.Material} material to use.
 * @param {number} size Width, height and depth of the cube.
 * @param {!o3djs.math.Matrix4} opt_matrix A matrix by which to multiply
 *     all the vertices.
 * @return {!o3d.Shape} The created cube.
 *
 * @see o3d.Pack
 * @see o3d.Shape
 */
o3djs.primitives.createRainbowCube = function(pack,
                                              material,
                                              size,
                                              opt_matrix) {
  var vertexInfo = o3djs.primitives.createCubeVertices(size, opt_matrix);
  var colorStream = vertexInfo.addStream(
      4, o3djs.base.o3d.Stream.COLOR);

  var colors = [
    [1, 0, 0, 1],
    [0, 1, 0, 1],
    [0, 0, 1, 1],
    [1, 1, 0, 1],
    [0, 1, 1, 1],
    [1, 0, 1, 1],
    [0, .5, .3, 1],
    [.3, 0, .5, 1]
  ];

  var vertices = vertexInfo.vertices;
  for (var f = 0; f < 6; ++f) {
    var faceIndices = o3djs.primitives.CUBE_FACE_INDICES_[f];
    for (var v = 0; v < 4; ++v) {
      var color = colors[faceIndices[v]];
      colorStream.addElementVector(color);
    }
  }

  return vertexInfo.createShape(pack, material);
};

/**
 * Creates disc vertices. The disc will be in the xz plane, centered
 * at the origin. When creating, at least 3 divisions, or pie pieces, need
 * to be specified, otherwise the triangles making up the disc will be
 * degenerate. You can also specify the number of radial pieces (opt_stacks).
 * A value of 1 for opt_stacks will give you a simple disc of pie pieces.  If
 * you want to create an annulus by omitting some of the center stacks, you
 * can specify the stack at which to start creating triangles.  Finally,
 * stackPower allows you to have the widths increase or decrease as you move
 * away from the center. This is particularly useful when using the disc as a
 * ground plane with a fixed camera such that you don't need the resolution of
 * small triangles near the perimeter.  For example, a value of 2 will produce
 * stacks whose ouside radius increases with the square of the stack index. A
 * value of 1 will give uniform stacks.
 *
 * @param {number} radius Radius of the ground plane.
 * @param {number} divisions Number of triangles in the ground plane
 *                 (at least 3).
 * @param {number} opt_stacks Number of radial divisions (default=1).
 * @param {number} opt_startStack Which radial division to start dividing at.
 * @param {number} opt_stackPower Power to raise stack size to for decreasing
 *                 width.
 * @param {!o3djs.math.Matrix4} opt_matrix A matrix by which to multiply
 *     all the vertices.
 * @return {!o3djs.primitives.VertexInfo} The created plane vertices.
 */
o3djs.primitives.createDiscVertices = function(radius,
                                               divisions,
                                               opt_stacks,
                                               opt_startStack,
                                               opt_stackPower,
                                               opt_matrix) {
  if (divisions < 3) {
    throw Error('divisions must be at least 3');
  }

  var stacks = opt_stacks ? opt_stacks : 1;
  var startStack = opt_startStack ? opt_startStack : 0;
  var stackPower = opt_stackPower ? opt_stackPower : 1;

  var vertexInfo = o3djs.primitives.createVertexInfo();
  var positionStream = vertexInfo.addStream(
      3, o3djs.base.o3d.Stream.POSITION);
  var normalStream = vertexInfo.addStream(
      3, o3djs.base.o3d.Stream.NORMAL);
  var texCoordStream = vertexInfo.addStream(
      2, o3djs.base.o3d.Stream.TEXCOORD, 0);

  // Initialize the center vertex.
  // x  y  z nx ny nz  r  g  b  a  u  v
  var firstIndex = 0;

  if (startStack == 0) {
    positionStream.addElement(0, 0, 0);
    normalStream.addElement(0, 1, 0);
    texCoordStream.addElement(0, 0);
    firstIndex++;
  }

  // Build the disk one stack at a time.
  for (var currentStack = Math.max(startStack, 1);
       currentStack <= stacks;
       ++currentStack) {
    var stackRadius = radius * Math.pow(currentStack / stacks, stackPower);

    for (var i = 0; i < divisions; ++i) {
      var theta = 2.0 * Math.PI * i / divisions;
      var x = stackRadius * Math.cos(theta);
      var z = stackRadius * Math.sin(theta);

      positionStream.addElement(x, 0, z);
      normalStream.addElement(0, 1, 0);
      texCoordStream.addElement(x, z);

      if (currentStack > startStack) {
        // a, b, c and d are the indices of the vertices of a quad.  unless
        // the current stack is the one closest to the center, in which case
        // the vertices a and b connect to the center vertex.
        var a = firstIndex + (i + 1) % divisions;
        var b = firstIndex + i;
        if (currentStack > 1) {
          var c = firstIndex + i - divisions;
          var d = firstIndex + (i + 1) % divisions - divisions;

          // Make a quad of the vertices a, b, c, d.
          vertexInfo.addTriangle(a, b, c);
          vertexInfo.addTriangle(a, c, d);
        } else {
          // Make a single triangle of a, b and the center.
          vertexInfo.addTriangle(0, a, b);
        }
      }
    }

    firstIndex += divisions;
  }

  if (opt_matrix) {
    vertexInfo.reorient(opt_matrix);
  }
  return vertexInfo;
};

/**
 * Creates a disc shape. The disc will be in the xz plane, centered
 * at the origin. When creating, at least 3 divisions, or pie pieces, need
 * to be specified, otherwise the triangles making up the disc will be
 * degenerate. You can also specify the number of radial pieces (opt_stacks).
 * A value of 1 for opt_stacks will give you a simple disc of pie pieces.  If
 * you want to create an annulus by omitting some of the center stacks, you
 * can specify the stack at which to start creating triangles.  Finally,
 * stackPower allows you to have the widths increase or decrease as you move
 * away from the center. This is particularly useful when using the disc as a
 * ground plane with a fixed camera such that you don't need the resolution of
 * small triangles near the perimeter.  For example, a value of 2 will produce
 * stacks whose ouside radius increases with the square of the stack index. A
 * value of 1 will give uniform stacks.
 *
 * @param {!o3d.Pack} pack Pack to create disc elements in.
 * @param {!o3d.Material} material to use.
 * @param {number} radius Radius of the disc.
 * @param {number} divisions Number of triangles in the disc (at least 3).
 * @param {number} stacks Number of radial divisions.
 * @param {number} startStack Which radial division to start dividing at.
 * @param {number} stackPower Power to raise stack size to for decreasing width.
 * @param {!o3djs.math.Matrix4} opt_matrix A matrix by which to multiply
 *     all the vertices.
 * @return {!o3d.Shape} The created disc.
 *
 * @see o3d.Pack
 * @see o3d.Shape
 */
o3djs.primitives.createDisc = function(pack, material,
                                       radius, divisions, stacks,
                                       startStack, stackPower,
                                       opt_matrix) {
  var vertexInfo = o3djs.primitives.createDiscVertices(radius, divisions,
                                                       stacks,
                                                       startStack,
                                                       stackPower,
                                                       opt_matrix);

  return vertexInfo.createShape(pack, material);
};

/**
 * Creates cylinder vertices. The cylinder will be created around the origin
 * along the y-axis. The created cylinder has position, normal and uv streams.
 *
 * @param {number} radius Radius of cylinder.
 * @param {number} height Height of cylinder.
 * @param {number} radialSubdivisions The number of subdivisions around the
 *     cylinder.
 * @param {number} verticalSubdivisions The number of subdivisions down the
 *     cylinder.
 * @param {!o3djs.math.Matrix4} opt_matrix A matrix by which to multiply
 *     all the vertices.
 * @return {!o3djs.primitives.VertexInfo} The created cylinder vertices.
 */
o3djs.primitives.createCylinderVertices = function(radius,
                                                   height,
                                                   radialSubdivisions,
                                                   verticalSubdivisions,
                                                   opt_matrix) {
  return o3djs.primitives.createTruncatedConeVertices(radius,
                                                      radius,
                                                      height,
                                                      radialSubdivisions,
                                                      verticalSubdivisions,
                                                      opt_matrix);
};

/**
 * Creates a cylinder shape. The cylinder will be created around the
 * origin along the y-axis. The created cylinder has position, normal
 * and uv streams.
 *
 * @param {!o3d.Pack} pack Pack to create cylinder elements in.
 * @param {!o3d.Material} material to use.
 * @param {number} radius Radius of cylinder.
 * @param {number} height Height of cylinder.
 * @param {number} radialSubdivisions The number of subdivisions around the
 *     cylinder.
 * @param {number} verticalSubdivisions The number of subdivisions down the
 *     cylinder.
 * @param {!o3djs.math.Matrix4} opt_matrix A matrix by which to multiply
 *     all the vertices.
 * @return {!o3d.Shape} The created cylinder.
 */
o3djs.primitives.createCylinder = function(pack,
                                           material,
                                           radius,
                                           height,
                                           radialSubdivisions,
                                           verticalSubdivisions,
                                           opt_matrix) {
  var vertexInfo = o3djs.primitives.createCylinderVertices(
      radius,
      height,
      radialSubdivisions,
      verticalSubdivisions,
      opt_matrix);
  return vertexInfo.createShape(pack, material);
};

/**
 * Creates vertices for a truncated cone, which is like a cylinder
 * except that it has different top and bottom radii. A truncated cone
 * can also be used to create cylinders and regular cones. The
 * truncated cone will be created centered about the origin, with the
 * y axis as its vertical axis. The created cone has position, normal
 * and uv streams.
 *
 * @param {number} bottomRadius Bottom radius of truncated cone.
 * @param {number} topRadius Top radius of truncated cone.
 * @param {number} height Height of truncated cone.
 * @param {number} radialSubdivisions The number of subdivisions around the
 *     truncated cone.
 * @param {number} verticalSubdivisions The number of subdivisions down the
 *     truncated cone.
 * @param {!o3djs.math.Matrix4} opt_matrix A matrix by which to multiply
 *     all the vertices.
 * @return {!o3djs.primitives.VertexInfo} The created truncated cone vertices.
 */
o3djs.primitives.createTruncatedConeVertices = function(bottomRadius,
                                                        topRadius,
                                                        height,
                                                        radialSubdivisions,
                                                        verticalSubdivisions,
                                                        opt_matrix) {
  if (radialSubdivisions < 3) {
    throw Error('radialSubdivisions must be 3 or greater');
  }

  if (verticalSubdivisions < 1) {
    throw Error('verticalSubdivisions must be 1 or greater');
  }

  var vertexInfo = o3djs.primitives.createVertexInfo();
  var positionStream = vertexInfo.addStream(
      3, o3djs.base.o3d.Stream.POSITION);
  var normalStream = vertexInfo.addStream(
      3, o3djs.base.o3d.Stream.NORMAL);
  var texCoordStream = vertexInfo.addStream(
      2, o3djs.base.o3d.Stream.TEXCOORD, 0);

  var vertsAroundEdge = radialSubdivisions + 1;

  // The slant of the cone is constant across its surface
  var slant = Math.atan2(bottomRadius - topRadius, height);
  var cosSlant = Math.cos(slant);
  var sinSlant = Math.sin(slant);

  for (var yy = -2; yy <= verticalSubdivisions + 2; ++yy) {
    var v = yy / verticalSubdivisions
    var y = height * v;
    var ringRadius;
    if (yy < 0) {
      y = 0;
      v = 1;
      ringRadius = bottomRadius;
    } else if (yy > verticalSubdivisions) {
      y = height;
      v = 1;
      ringRadius = topRadius;
    } else {
      ringRadius = bottomRadius +
        (topRadius - bottomRadius) * (yy / verticalSubdivisions);
    }
    if (yy == -2 || yy == verticalSubdivisions + 2) {
      ringRadius = 0;
      v = 0;
    }
    y -= height / 2;
    for (var ii = 0; ii < vertsAroundEdge; ++ii) {
      var sin = Math.sin(ii * Math.PI * 2 / radialSubdivisions);
      var cos = Math.cos(ii * Math.PI * 2 / radialSubdivisions);
      positionStream.addElement(sin * ringRadius, y, cos * ringRadius);
      normalStream.addElement(
          (yy < 0 || yy > verticalSubdivisions) ? 0 : (sin * cosSlant),
          (yy < 0) ? -1 : (yy > verticalSubdivisions ? 1 : sinSlant),
          (yy < 0 || yy > verticalSubdivisions) ? 0 : (cos * cosSlant));
      texCoordStream.addElement(ii / radialSubdivisions, v);
    }
  }

  for (var yy = 0; yy < verticalSubdivisions + 4; ++yy) {
    for (var ii = 0; ii < radialSubdivisions; ++ii) {
      vertexInfo.addTriangle(vertsAroundEdge * (yy + 0) + 0 + ii,
                             vertsAroundEdge * (yy + 0) + 1 + ii,
                             vertsAroundEdge * (yy + 1) + 1 + ii);
      vertexInfo.addTriangle(vertsAroundEdge * (yy + 0) + 0 + ii,
                             vertsAroundEdge * (yy + 1) + 1 + ii,
                             vertsAroundEdge * (yy + 1) + 0 + ii);
    }
  }

  if (opt_matrix) {
    vertexInfo.reorient(opt_matrix);
  }
  return vertexInfo;
};

/**
 * Creates a truncated cone shape, which is like a cylinder except
 * that it has different top and bottom radii. A truncated cone can
 * also be used to create cylinders, by setting the bottom and top
 * radii equal, and cones, by setting either the top or bottom radius
 * to 0. The truncated cone will be created centered about the origin,
 * with the y axis as its vertical axis. The created cone has
 * position, normal and uv streams.
 *
 * @param {!o3d.Pack} pack Pack in which to create the truncated cone.
 * @param {!o3d.Material} material to use.
 * @param {number} bottomRadius Bottom radius of truncated cone.
 * @param {number} topRadius Top radius of truncated cone.
 * @param {number} height Height of truncated cone.
 * @param {number} radialSubdivisions The number of subdivisions around the
 *     truncated cone.
 * @param {number} verticalSubdivisions The number of subdivisions down the
 *     truncated cone.
 * @param {!o3djs.math.Matrix4} opt_matrix A matrix by which to multiply
 *     all the vertices.
 * @return {!o3d.Shape} The created truncated cone.
 */
o3djs.primitives.createTruncatedCone = function(pack,
                                                material,
                                                bottomRadius,
                                                topRadius,
                                                height,
                                                radialSubdivisions,
                                                verticalSubdivisions,
                                                opt_matrix) {
  var vertexInfo = o3djs.primitives.createTruncatedConeVertices(
      bottomRadius,
      topRadius,
      height,
      radialSubdivisions,
      verticalSubdivisions,
      opt_matrix);
  return vertexInfo.createShape(pack, material);
};

/**
 * Creates vertices for a torus. The torus will be created centered about the
 * origin, with the y axis as its vertical axis. The created torus has
 * position, normal and uv streams.
 *
 * @param {number} torusRadius Distance from the center of the tube to
 *     the center of the torus.
 * @param {number} tubeRadius Radius of the tube.
 * @param {number} tubeLengthSubdivisions The number of subdivisions around the
 *     vertical axis of the torus, i.e. along the length of the tube.
 * @param {number} circleSubdivisions The number of subdivisions in the circle
 *     that is rotated about the vertical axis to create the torus.
 * @param {!o3djs.math.Matrix4} opt_matrix A matrix by which to multiply
 *     all the vertices.
 * @return {!o3djs.primitives.VertexInfo} The created torus vertices.
 */
o3djs.primitives.createTorusVertices = function(torusRadius,
                                                tubeRadius,
                                                tubeLengthSubdivisions,
                                                circleSubdivisions,
                                                opt_matrix) {
  if (tubeLengthSubdivisions < 3) {
    throw Error('tubeLengthSubdivisions must be 3 or greater');
  }

  if (circleSubdivisions < 3) {
    throw Error('circleSubdivisions must be 3 or greater');
  }

  var vertexInfo = o3djs.primitives.createVertexInfo();
  var positionStream = vertexInfo.addStream(
      3, o3djs.base.o3d.Stream.POSITION);
  var normalStream = vertexInfo.addStream(
      3, o3djs.base.o3d.Stream.NORMAL);
  var texCoordStream = vertexInfo.addStream(
      2, o3djs.base.o3d.Stream.TEXCOORD, 0);

  for (var uu = 0; uu < tubeLengthSubdivisions; ++uu) {
    var u = (uu / tubeLengthSubdivisions) * 2 * Math.PI;
    for (var vv = 0; vv < circleSubdivisions; ++vv) {
      var v = (vv / circleSubdivisions) * 2 * Math.PI;
      var sinu = Math.sin(u);
      var cosu = Math.cos(u);
      var sinv = Math.sin(v);
      var cosv = Math.cos(v);
      positionStream.addElement((torusRadius + tubeRadius * cosv) * cosu,
                                tubeRadius * sinv,
                                (torusRadius + tubeRadius * cosv) * sinu);
      normalStream.addElement(cosv * cosu,
                              sinv,
                              cosv * sinu);
      texCoordStream.addElement(uu / tubeLengthSubdivisions,
                                vv / circleSubdivisions);
    }
  }

  for (var uu = 0; uu < tubeLengthSubdivisions; ++uu) {
    for (var vv = 0; vv < circleSubdivisions; ++vv) {
      // We want to wrap the indices around at the seams.
      var uuPlusOne = (uu + 1) % tubeLengthSubdivisions;
      var vvPlusOne = (vv + 1) % circleSubdivisions;
      // The indices of four points forming a quad.
      var a = circleSubdivisions * uu        + vv;
      var b = circleSubdivisions * uuPlusOne + vv;
      var c = circleSubdivisions * uu        + vvPlusOne;
      var d = circleSubdivisions * uuPlusOne + vvPlusOne;
      vertexInfo.addTriangle(a, d, b);
      vertexInfo.addTriangle(a, c, d);
    }
  }

  if (opt_matrix) {
    vertexInfo.reorient(opt_matrix);
  }
  return vertexInfo;
};

/**
 * Creates a torus shape. The torus will be created centered about the
 * origin, with the y axis as its vertical axis. The created torus has
 * position, normal and uv streams.
 *
 * @param {!o3d.Pack} pack Pack in which to create the torus.
 * @param {!o3d.Material} material to use.
 * @param {number} torusRadius Distance from the center of the tube to
 *     the center of the torus.
 * @param {number} tubeRadius Radius of the tube.
 * @param {number} tubeLengthSubdivisions The number of subdivisions around the
 *     vertical axis of the torus, i.e. along the length of the tube.
 * @param {number} circleSubdivisions The number of subdivisions in the circle
 *     that is rotated about the vertical axis to create the torus.
 * @param {!o3djs.math.Matrix4} opt_matrix A matrix by which to multiply
 *     all the vertices.
 * @return {!o3d.Shape} The created torus.
 */
o3djs.primitives.createTorus = function(pack,
                                        material,
                                        torusRadius,
                                        tubeRadius,
                                        tubeLengthSubdivisions,
                                        circleSubdivisions,
                                        opt_matrix) {
  var vertexInfo = o3djs.primitives.createTorusVertices(
      torusRadius,
      tubeRadius,
      tubeLengthSubdivisions,
      circleSubdivisions,
      opt_matrix);
  return vertexInfo.createShape(pack, material);
};

/**
 * Creates wedge vertices, wedge being an extruded triangle. The wedge will be
 * created around the 3 2d points passed in and extruded along the z axis. The
 * created wedge has position, normal and uv streams.
 *
 * @param {!Array.<!Array.<number>>} inPoints Array of 2d points in the format
 *   [[x1, y1], [x2, y2], [x3, y3]] that describe a 2d triangle.
 * @param {number} depth The depth to extrude the triangle.
 * @param {!o3djs.math.Matrix4} opt_matrix A matrix by which to multiply
 *     all the vertices.
 * @return {!o3djs.primitives.VertexInfo} The created wedge vertices.
 */
o3djs.primitives.createWedgeVertices = function(inPoints, depth,
                                                opt_matrix) {
  var math = o3djs.math;

  var vertexInfo = o3djs.primitives.createVertexInfo();
  var positionStream = vertexInfo.addStream(
      3, o3djs.base.o3d.Stream.POSITION);
  var normalStream = vertexInfo.addStream(
      3, o3djs.base.o3d.Stream.NORMAL);
  var texCoordStream = vertexInfo.addStream(
      2, o3djs.base.o3d.Stream.TEXCOORD, 0);

  var z1 = -depth * 0.5;
  var z2 = depth * 0.5;
  var face = [];
  var points = [[inPoints[0][0], inPoints[0][1]],
                [inPoints[1][0], inPoints[1][1]],
                [inPoints[2][0], inPoints[2][1]]];

  face[0] = math.cross(
      math.normalize([points[1][0] - points[0][0],
                      points[1][1] - points[0][1],
                      z1 - z1]),
      math.normalize([points[1][0] - points[1][0],
                      points[1][1] - points[1][1],
                      z2 - z1]));
  face[1] = math.cross(
      math.normalize([points[2][0] - points[1][0],
                      points[2][1] - points[1][1],
                      z1 - z1]),
      math.normalize([points[2][0] - points[2][0],
                      points[2][1] - points[2][1],
                      z2 - z1]));
  face[2] = math.cross(
      [points[0][0] - points[2][0], points[0][1] - points[2][1], z1 - z1],
      [points[0][0] - points[0][0], points[0][1] - points[0][1], z2 - z1]);

  positionStream.addElement(points[0][0], points[0][1], z1);
  normalStream.addElement(0, 0, -1);
  texCoordStream.addElement(0, 1);
  positionStream.addElement(points[1][0], points[1][1], z1);
  normalStream.addElement(0, 0, -1);
  texCoordStream.addElement(1, 0);
  positionStream.addElement(points[2][0], points[2][1], z1);
  normalStream.addElement(0, 0, -1);
  texCoordStream.addElement(0, 0);
                  // back
  positionStream.addElement(points[0][0], points[0][1], z2);
  normalStream.addElement(0, 0, 1);
  texCoordStream.addElement(0, 1);
  positionStream.addElement(points[1][0], points[1][1], z2);
  normalStream.addElement(0, 0, 1);
  texCoordStream.addElement(1, 0);
  positionStream.addElement(points[2][0], points[2][1], z2);
  normalStream.addElement(0, 0, 1);
  texCoordStream.addElement(0, 0);
                  // face 0
  positionStream.addElement(points[0][0], points[0][1], z1);
  normalStream.addElement(face[0][0], face[0][1], face[0][2]);
  texCoordStream.addElement(0, 1);
  positionStream.addElement(points[1][0], points[1][1], z1);
  normalStream.addElement(face[0][0], face[0][1], face[0][2]);
  texCoordStream.addElement(0, 0);
  positionStream.addElement(points[1][0], points[1][1], z2);
  normalStream.addElement(face[0][0], face[0][1], face[0][2]);
  texCoordStream.addElement(1, 0);
  positionStream.addElement(points[0][0], points[0][1], z2);
  normalStream.addElement(face[0][0], face[0][1], face[0][2]);
  texCoordStream.addElement(1, 1);
                  // face 1
  positionStream.addElement(points[1][0], points[1][1], z1);
  normalStream.addElement(face[1][0], face[1][1], face[1][2]);
  texCoordStream.addElement(0, 1);
  positionStream.addElement(points[2][0], points[2][1], z1);
  normalStream.addElement(face[1][0], face[1][1], face[1][2]);
  texCoordStream.addElement(0, 0);
  positionStream.addElement(points[2][0], points[2][1], z2);
  normalStream.addElement(face[1][0], face[1][1], face[1][2]);
  texCoordStream.addElement(1, 0);
  positionStream.addElement(points[1][0], points[1][1], z2);
  normalStream.addElement(face[1][0], face[1][1], face[1][2]);
  texCoordStream.addElement(1, 1);
                  // face 2
  positionStream.addElement(points[2][0], points[2][1], z1);
  normalStream.addElement(face[2][0], face[2][1], face[2][2]);
  texCoordStream.addElement(0, 1);
  positionStream.addElement(points[0][0], points[0][1], z1);
  normalStream.addElement(face[2][0], face[2][1], face[2][2]);
  texCoordStream.addElement(0, 0);
  positionStream.addElement(points[0][0], points[0][1], z2);
  normalStream.addElement(face[2][0], face[2][1], face[2][2]);
  texCoordStream.addElement(1, 0);
  positionStream.addElement(points[2][0], points[2][1], z2);
  normalStream.addElement(face[2][0], face[2][1], face[2][2]);
  texCoordStream.addElement(1, 1);

  vertexInfo.addTriangle(0, 2, 1);
  vertexInfo.addTriangle(3, 4, 5);
  vertexInfo.addTriangle(6, 7, 8);
  vertexInfo.addTriangle(6, 8, 9);
  vertexInfo.addTriangle(10, 11, 12);
  vertexInfo.addTriangle(10, 12, 13);
  vertexInfo.addTriangle(14, 15, 16);
  vertexInfo.addTriangle(14, 16, 17);

  if (opt_matrix) {
    vertexInfo.reorient(opt_matrix);
  }
  return vertexInfo;
};

/**
 * Creates a wedge shape. A wedge being an extruded triangle. The wedge will
 * be created around the 3 2d points passed in and extruded along the z-axis.
 * The created wedge has position, normal and uv streams.
 *
 * @param {!o3d.Pack} pack Pack to create wedge elements in.
 * @param {!o3d.Material} material to use.
 * @param {!Array.<!Array.<number>>} points Array of 2d points in the format
 *     [[x1, y1], [x2, y2], [x3, y3]] that describe a 2d triangle.
 * @param {number} depth The depth to extrude the triangle.
 * @param {!o3djs.math.Matrix4} opt_matrix A matrix by which to multiply
 *     all the vertices.
 * @return {!o3d.Shape} The created wedge.
 */
o3djs.primitives.createWedge = function(pack,
                                        material,
                                        points,
                                        depth,
                                        opt_matrix) {
  var vertexInfo = o3djs.primitives.createWedgeVertices(points,
                                                        depth,
                                                        opt_matrix);
  return vertexInfo.createShape(pack, material);
};

/**
 * Creates prism vertices by extruding a polygon. The prism will be created
 * around the 2d points passed in and extruded along the z axis.  The end caps
 * of the prism are constructed using a triangle fan originating at point 0,
 * so a non-convex polygon might not get the desired shape, but it will if it
 * is convex with respect to point 0.  Texture coordinates map each face of
 * the wall exactly to the unit square.  Texture coordinates on the front
 * and back faces are scaled such that the bounding rectangle of the polygon
 * is mapped to the unit square. The created prism has position, normal,
 * uv streams.
 *
 * @param {!Array.<!Array.<number>>} points Array of 2d points in the format
 *     [[x1, y1], [x2, y2], [x3, y3],...] that describe a 2d polygon.
 * @param {number} depth The depth to extrude the polygon.
 * @param {!o3djs.math.Matrix4} opt_matrix A matrix by which to multiply
 *     all the vertices.
 * @return {!o3djs.primitives.VertexInfo} The created prism vertices.
 */
o3djs.primitives.createPrismVertices = function(points,
                                                depth,
                                                opt_matrix) {
  if (points.length < 3) {
    throw Error('there must be 3 or more points');
  }

  var backZ = -0.5 * depth;
  var frontZ = 0.5 * depth;
  var normals = [];

  var vertexInfo = o3djs.primitives.createVertexInfo();
  var positionStream = vertexInfo.addStream(
      3, o3djs.base.o3d.Stream.POSITION);
  var normalStream = vertexInfo.addStream(
      3, o3djs.base.o3d.Stream.NORMAL);
  var texCoordStream = vertexInfo.addStream(
      2, o3djs.base.o3d.Stream.TEXCOORD, 0);

  // Normals for the wall faces.
  var n = points.length;

  for (var i = 0; i < n; ++i) {
    var j = (i + 1) % n;
    var x = points[j][0] - points[i][0];
    var y = points[j][1] - points[i][1];
    var length = Math.sqrt(x * x + y * y);
    normals[i] = [y / length, -x / length, 0];
  }

  // Compute the minimum and maxiumum x and y coordinates of points in the
  // polygon.
  var minX = points[0][0];
  var minY = points[0][1];
  var maxX = points[0][0];
  var maxY = points[0][1];
  for (var i = 1; i < n; ++i) {
    var x = points[i][0];
    var y = points[i][1];
    minX = Math.min(minX, x);
    minY = Math.min(minY, y);
    maxX = Math.max(maxX, x);
    maxY = Math.max(maxY, y);
  }

  // Scale the x and y coordinates of the points of the polygon to fit the
  // bounding rectangle, and use the scaled coordinates for the uv
  // of the front and back cap.
  var frontUV = [];
  var backUV = [];
  var rangeX = maxX - minX;
  var rangeY = maxY - minY;
  for (var i = 0; i < n; ++i) {
    frontUV[i] = [
      (points[i][0] - minX) / rangeX,
      (points[i][1] - minY) / rangeY
    ];
    backUV[i] = [
      (maxX - points[i][0]) / rangeX,
      (points[i][1] - minY) / rangeY
    ];
  }

  for (var i = 0; i < n; ++i) {
    var j = (i + 1) % n;
    // Vertex on the back face.
    positionStream.addElement(points[i][0], points[i][1], backZ);
    normalStream.addElement(0, 0, -1);
    texCoordStream.addElement(backUV[i][0], backUV[i][1]);

    // Vertex on the front face.
    positionStream.addElement(points[i][0], points[i][1], frontZ),
    normalStream.addElement(0, 0, 1);
    texCoordStream.addElement(frontUV[i][0], frontUV[i][1]);

    // Vertices for a quad on the wall.
    positionStream.addElement(points[i][0], points[i][1], backZ),
    normalStream.addElement(normals[i][0], normals[i][1], normals[i][2]);
    texCoordStream.addElement(0, 1);

    positionStream.addElement(points[j][0], points[j][1], backZ),
    normalStream.addElement(normals[i][0], normals[i][1], normals[i][2]);
    texCoordStream.addElement(0, 0);

    positionStream.addElement(points[j][0], points[j][1], frontZ),
    normalStream.addElement(normals[i][0], normals[i][1], normals[i][2]);
    texCoordStream.addElement(1, 0);

    positionStream.addElement(points[i][0], points[i][1], frontZ),
    normalStream.addElement(normals[i][0], normals[i][1], normals[i][2]);
    texCoordStream.addElement(1, 1);

    if (i > 0 && i < n - 1) {
      // Triangle for the back face.
      vertexInfo.addTriangle(0, 6 * (i + 1), 6 * i);

      // Triangle for the front face.
      vertexInfo.addTriangle(1, 6 * i + 1, 6 * (i + 1) + 1);
    }

    // Quad on the wall.
    vertexInfo.addTriangle(6 * i + 2, 6 * i + 3, 6 * i + 4);
    vertexInfo.addTriangle(6 * i + 2, 6 * i + 4, 6 * i + 5);
  }

  if (opt_matrix) {
    vertexInfo.reorient(opt_matrix);
  }
  return vertexInfo;
};

/**
 * Creates a prism shape by extruding a polygon. The prism will be created
 * around the 2d points passed in an array and extruded along the z-axis.
 * The end caps of the prism are constructed using a triangle fan originating
 * at the first point, so a non-convex polygon might not get the desired
 * shape, but it will if it is convex with respect to the first point.
 * Texture coordinates map each face of the wall exactly to the unit square.
 * Texture coordinates on the front and back faces are scaled such that the
 * bounding rectangle of the polygon is mapped to the unit square.
 * The created prism has position, normal and uv streams.
 *
 * @param {!o3d.Pack} pack Pack to create wedge elements in.
 * @param {!o3d.Material} material to use.
 * @param {!Array.<!Array.<number>>} points Array of 2d points in the format:
 *     [[x1, y1], [x2, y2], [x3, y3],...] that describe a 2d polygon.
 * @param {number} depth The depth to extrude the polygon.
 * @param {!o3djs.math.Matrix4} opt_matrix A matrix by which to multiply
 *     all the vertices.
 * @return {!o3d.Shape} The created prism.
 */
o3djs.primitives.createPrism = function(pack,
                                        material,
                                        points,
                                        depth,
                                        opt_matrix) {
  var vertexInfo = o3djs.primitives.createPrismVertices(points,
                                                        depth,
                                                        opt_matrix);
  return vertexInfo.createShape(pack, material);
};

/**
 * Creates XZ plane vertices.
 * The created plane has position, normal and uv streams.
 *
 * @param {number} width Width of the plane.
 * @param {number} depth Depth of the plane.
 * @param {number} subdivisionsWidth Number of steps across the plane.
 * @param {number} subdivisionsDepth Number of steps down the plane.
 * @param {!o3djs.math.Matrix4} opt_matrix A matrix by which to multiply
 *     all the vertices.
 * @return {!o3djs.primitives.VertexInfo} The created plane vertices.
 */
o3djs.primitives.createPlaneVertices = function(width,
                                                depth,
                                                subdivisionsWidth,
                                                subdivisionsDepth,
                                                opt_matrix) {
  if (subdivisionsWidth <= 0 || subdivisionsDepth <= 0) {
    throw Error('subdivisionWidth and subdivisionDepth must be > 0');
  }

  var vertexInfo = o3djs.primitives.createVertexInfo();
  var positionStream = vertexInfo.addStream(
      3, o3djs.base.o3d.Stream.POSITION);
  var normalStream = vertexInfo.addStream(
      3, o3djs.base.o3d.Stream.NORMAL);
  var texCoordStream = vertexInfo.addStream(
      2, o3djs.base.o3d.Stream.TEXCOORD, 0);

  // Generate the individual vertices in our vertex buffer.
  for (var z = 0; z <= subdivisionsDepth; z++) {
    for (var x = 0; x <= subdivisionsWidth; x++) {
      var u = x / subdivisionsWidth;
      var v = z / subdivisionsDepth;
      positionStream.addElement(width * u - width * 0.5,
                                0,
                                depth * v - depth * 0.5);
      normalStream.addElement(0, 1, 0);
      texCoordStream.addElement(u, 1 - v);
    }
  }

  var numVertsAcross = subdivisionsWidth + 1;

  for (var z = 0; z < subdivisionsDepth; z++) {
    for (var x = 0; x < subdivisionsWidth; x++) {
      // triangle 1 of quad
      vertexInfo.addTriangle(
          (z + 0) * numVertsAcross + x,
          (z + 1) * numVertsAcross + x,
          (z + 0) * numVertsAcross + x + 1);

      // triangle 2 of quad
      vertexInfo.addTriangle(
          (z + 1) * numVertsAcross + x,
          (z + 1) * numVertsAcross + x + 1,
          (z + 0) * numVertsAcross + x + 1);
    }
  }

  if (opt_matrix) {
    vertexInfo.reorient(opt_matrix);
  }
  return vertexInfo;
};

/**
 * Creates an XZ plane.
 * The created plane has position, normal and uv streams.
 *
 * @param {!o3d.Pack} pack Pack to create plane elements in.
 * @param {!o3d.Material} material to use.
 * @param {number} width Width of the plane.
 * @param {number} depth Depth of the plane.
 * @param {number} subdivisionsWidth Number of steps across the plane.
 * @param {number} subdivisionsDepth Number of steps down the plane.
 * @param {!o3djs.math.Matrix4} opt_matrix A matrix by which to multiply
 *     all the vertices.
 * @return {!o3d.Shape} The created plane.
 *
 * @see o3d.Pack
 * @see o3d.Shape
 */
o3djs.primitives.createPlane = function(pack,
                                        material,
                                        width,
                                        depth,
                                        subdivisionsWidth,
                                        subdivisionsDepth,
                                        opt_matrix) {
  var vertexInfo = o3djs.primitives.createPlaneVertices(
      width,
      depth,
      subdivisionsWidth,
      subdivisionsDepth,
      opt_matrix);

  return vertexInfo.createShape(pack, material);
};

/**
 * Creates an XZ fade plane, where the alpha channel of the color stream
 * fades from 1 to 0.
 * The created plane has position, normal, uv and vertex color streams.
 *
 * @param {!o3d.Pack} pack Pack to create plane elements in.
 * @param {!o3d.Material} material to use.
 * @param {number} width Width of the plane.
 * @param {number} depth Depth of the plane.
 * @param {number} subdivisionsWidth Number of steps across the plane.
 * @param {number} subdivisionsDepth Number of steps down the plane.
 * @param {!o3djs.math.Matrix4} opt_matrix A matrix by which to multiply
 *     all the vertices.
 * @return {!o3d.Shape} The created plane.
 *
 * @see o3d.Pack
 * @see o3d.Shape
 */
o3djs.primitives.createFadePlane = function(pack,
                                            material,
                                            width,
                                            depth,
                                            subdivisionsWidth,
                                            subdivisionsDepth,
                                            opt_matrix) {
  var vertexInfo = o3djs.primitives.createPlaneVertices(
      width,
      depth,
      subdivisionsWidth,
      subdivisionsDepth,
      opt_matrix);
  var colorStream = vertexInfo.addStream(4, o3djs.base.o3d.Stream.COLOR);
  for (var z = 0; z <= subdivisionsDepth; z++) {
    var alpha = z / subdivisionsDepth;
    for (var x = 0; x <= subdivisionsWidth; x++) {
      colorStream.addElement(1, 1, 1, alpha);
    }
  }
  return vertexInfo.createShape(pack, material);
};

/*
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview This file contains various functions for helping create
 * render graphs for o3d.  It puts them in the "rendergraph" module on
 * the o3djs object.
 *
 *     Note: This library is only a sample. It is not meant to be some official
 *     library. It is provided only as example code.
 *
 */

o3djs.provide('o3djs.rendergraph');

/**
 * A Module for creating render graphs.
 * @namespace
 */
o3djs.rendergraph = o3djs.rendergraph || {};

/**
 * Creates a basic render graph setup to draw opaque and transparent
 * 3d objects.
 * @param {!o3d.Pack} pack Pack to manage created objects.
 * @param {!o3d.Transform} treeRoot root Transform of tree to render.
 * @param {!o3d.RenderNode} opt_parent RenderNode to build this view under.
 * @param {!o3djs.math.Vector4} opt_clearColor color to clear view.
 * @param {number} opt_priority Optional base priority for created objects.
 * @param {!o3djs.math.Vector4} opt_viewport viewport settings for view.
 * @param {!o3d.DrawList} opt_performanceDrawList Optional DrawList to
 *     use for performanceDrawPass.
 * @param {!o3d.DrawList} opt_zOrderedDrawList Optional DrawList to
 *     use for zOrderedDrawPass.
 * @param {!o3d.DrawContext} opt_drawContext Optional DrawContext to
 *     use. If not passed in one is created.
 * @return {!o3djs.rendergraph.ViewInfo} A ViewInfo object with info about
 *         everything created.
 */
o3djs.rendergraph.createView = function(pack,
                                        treeRoot,
                                        opt_parent,
                                        opt_clearColor,
                                        opt_priority,
                                        opt_viewport,
                                        opt_performanceDrawList,
                                        opt_zOrderedDrawList,
                                        opt_drawContext) {
  return new o3djs.rendergraph.ViewInfo(pack,
                                        treeRoot,
                                        opt_parent,
                                        opt_clearColor,
                                        opt_priority,
                                        opt_viewport,
                                        opt_performanceDrawList,
                                        opt_zOrderedDrawList,
                                        opt_drawContext);
};

/**
 * Creates a basic render graph setup to draw opaque and transparent
 * 3d objects.
 * @param {!o3d.Pack} pack Pack to manage created objects.
 * @param {!o3d.Transform} treeRoot root Transform of tree to render.
 * @param {!o3d.RenderNode} opt_parent RenderNode to build this view under.
 * @param {!o3djs.math.Vector4} opt_clearColor color to clear view.
 * @param {number} opt_priority Optional base priority for created objects.
 * @param {!o3djs.math.Vector4} opt_viewport viewport settings for view.
 * @return {!o3djs.rendergraph.ViewInfo} A ViewInfo object with info about
 *     everything created.
 */
o3djs.rendergraph.createBasicView = function(pack,
                                             treeRoot,
                                             opt_parent,
                                             opt_clearColor,
                                             opt_priority,
                                             opt_viewport) {
   return o3djs.rendergraph.createView(pack,
                                       treeRoot,
                                       opt_parent,
                                       opt_clearColor,
                                       opt_priority,
                                       opt_viewport);
};

/**
 * Creates an extra view render graph setup to draw opaque and transparent
 * 3d objects based on a previously created view. It uses the previous view
 * to share draw lists and to set the priority.
 * @param {!o3djs.rendergraph.ViewInfo} viewInfo ViewInfo returned from
 *     createBasicView.
 * @param {!o3djs.math.Vector4} opt_viewport viewport settings for view.
 * @param {!o3djs.math.Vector4} opt_clearColor color to clear view.
 * @param {number} opt_priority base priority for created objects.
 * @return {!o3djs.rendergraph.ViewInfo} A ViewInfo object with info about
 *     everything created.
 */
o3djs.rendergraph.createExtraView = function(viewInfo,
                                             opt_viewport,
                                             opt_clearColor,
                                             opt_priority) {
  return o3djs.rendergraph.createView(viewInfo.pack,
                                      viewInfo.treeRoot,
                                      viewInfo.renderGraphRoot,
                                      opt_clearColor,
                                      opt_priority,
                                      opt_viewport,
                                      viewInfo.performanceDrawList,
                                      viewInfo.zOrderedDrawList);
};

/**
 * A ViewInfo object creates the standard o3d objects needed for
 * a single 3d view. Those include a ClearBuffer followed by a TreeTraveral
 * followed by 2 DrawPasses all of which are children of a Viewport. On top of
 * those a DrawContext and optionally 2 DrawLists although you can pass in your
 * own DrawLists if there is a reason to reuse the same DrawLists such was with
 * mulitple views of the same scene.
 *
 * The render graph created is something like:
 * <pre>
 *        [Viewport]
 *            |
 *     +------+--------+------------------+---------------------+
 *     |               |                  |                     |
 * [ClearBuffer] [TreeTraversal] [Performance StateSet] [ZOrdered StateSet]
 *                                        |                     |
 *                               [Performance DrawPass] [ZOrdered DrawPass]
 * </pre>
 *
 * @constructor
 * @param {!o3d.Pack} pack Pack to manage created objects.
 * @param {!o3d.Transform} treeRoot root Transform of tree to render.
 * @param {!o3d.RenderNode} opt_parent RenderNode to build this view under.
 * @param {!o3djs.math.Vector4} opt_clearColor color to clear view.
 * @param {number} opt_priority Optional base priority for created objects.
 * @param {!o3djs.math.Vector4} opt_viewport viewport settings for view.
 * @param {!o3d.DrawList} opt_performanceDrawList DrawList to use for
 *     performanceDrawPass.
 * @param {!o3d.DrawList} opt_zOrderedDrawList DrawList to use for
 *     zOrderedDrawPass.
 * @param {!o3d.DrawContext} opt_drawContext Optional DrawContext to
 *     use. If not passed in one is created.
 */
o3djs.rendergraph.ViewInfo = function(pack,
                                      treeRoot,
                                      opt_parent,
                                      opt_clearColor,
                                      opt_priority,
                                      opt_viewport,
                                      opt_performanceDrawList,
                                      opt_zOrderedDrawList,
                                      opt_drawContext) {
  var that = this;
  var clearColor = opt_clearColor || [0.5, 0.5, 0.5, 1.0];
  var viewPriority = opt_priority || 0;
  var priority = 0;

  // Create Viewport.
  var viewport = pack.createObject('Viewport');
  if (opt_viewport) {
    viewport.viewport = opt_viewport;
  }
  viewport.priority = viewPriority;

  // Create a clear buffer.
  var clearBuffer = pack.createObject('ClearBuffer');
  clearBuffer.clearColor = clearColor;
  clearBuffer.priority = priority++;
  clearBuffer.parent = viewport;

  // Creates a TreeTraversal and parents it to the root.
  var treeTraversal = pack.createObject('TreeTraversal');
  treeTraversal.priority = priority++;
  treeTraversal.parent = viewport;
  treeTraversal.transform = treeRoot;

  this.drawPassInfos_ = [];

  /**
   * Pack that manages the objects created for this ViewInfo.
   * @type {!o3d.Pack}
   */
  this.pack = pack;

  /**
   * The RenderNode this ViewInfo render graph subtree is parented under.
   * @type {(!o3d.RenderNode|undefined)}
   */
  this.renderGraphRoot = opt_parent;

  /**
   * The root node of the transform graph this ViewInfo renders.
   * @type {!o3d.Transform}
   */
  this.treeRoot = treeRoot;

  /**
   * The root of the subtree of the render graph this ViewInfo is managing.
   * If you want to set the priority of a ViewInfo's rendergraph subtree use
   * <pre>
   * viewInfo.root.priority = desiredPriority;
   * </pre>
   * @type {!o3d.RenderNode}
   */
  this.root = viewport;

  /**
   * The Viewport RenderNode created for this ViewInfo.
   * @type {!o3d.Viewport}
   */
  this.viewport = viewport;

  /**
   * The ClearBuffer RenderNode created for this ViewInfo.
   * @type {!o3d.ClearBuffer}
   */
  this.clearBuffer = clearBuffer;

  // Create DrawContext.
  var drawContext = opt_drawContext || pack.createObject('DrawContext');

  /**
   * The DrawContext used by this ViewInfo.
   * @type {!o3d.DrawContext}
   */
  this.drawContext = drawContext;

  /**
   * The TreeTraversal used by this ViewInfo.
   * @type {!o3d.TreeTraversal}
   */
  this.treeTraversal = treeTraversal;

  /**
   * The highest priority used for objects under the Viewport RenderNode created
   * by this ViewInfo.
   * @type {number}
   */
  this.priority = priority;

  /**
   * This function is here just because the inside use case of
   * ViewInfo.createDrawPass is the less common case.
   * @param {o3d.DrawList.SortMethod} sortMethod how to sort.
   * @param {!o3d.DrawList} opt_drawList DrawList to use.
   */
  function createDrawPass(sortMethod, opt_drawList) {
    return that.createDrawPass(
        sortMethod,
        undefined,
        undefined,
        undefined,
        opt_drawList);
  }

  // Setup a Performance Ordered DrawPass
  var performanceDrawPassInfo = createDrawPass(
      o3djs.base.o3d.DrawList.BY_PERFORMANCE,
      opt_performanceDrawList);

  var performanceState = performanceDrawPassInfo.state;

  // Setup a z Ordered DrawPass
  var zOrderedDrawPassInfo = createDrawPass(
      o3djs.base.o3d.DrawList.BY_Z_ORDER,
      opt_zOrderedDrawList);

  var zOrderedState = zOrderedDrawPassInfo.state;

  zOrderedState.getStateParam('AlphaBlendEnable').value = true;
  zOrderedState.getStateParam('SourceBlendFunction').value =
      o3djs.base.o3d.State.BLENDFUNC_SOURCE_ALPHA;
  zOrderedState.getStateParam('DestinationBlendFunction').value =
      o3djs.base.o3d.State.BLENDFUNC_INVERSE_SOURCE_ALPHA;
  zOrderedState.getStateParam('AlphaTestEnable').value = true;
  zOrderedState.getStateParam('AlphaComparisonFunction').value =
      o3djs.base.o3d.State.CMP_GREATER;

  // Parent whatever the root is to the parent passed in.
  if (opt_parent) {
    this.root.parent = opt_parent;
  }

  /**
   * The DrawPassInfo for the performance draw pass.
   * @type {!o3djs.rendergraph.DrawPassInfo}
   */
  this.performanceDrawPassInfo = performanceDrawPassInfo;

  /**
   * The DrawPassInfo for the zOrdered draw pass.
   * @type {!o3djs.rendergraph.DrawPassInfo}
   */
  this.zOrderedDrawPassInfo = zOrderedDrawPassInfo;

  // Legacy properties

  /**
   * The StateSet RenderNode above the performance DrawPass in this ViewInfo
   * @type {!o3d.StateSet}
   */
  this.performanceStateSet = performanceDrawPassInfo.stateSet;

  /**
   * The State object used by the performanceStateSet object in this ViewInfo.
   * By default, no states are set here.
   * @type {!o3d.State}
   */
  this.performanceState = performanceState;

  /**
   * The DrawList used for the performance draw pass. Generally for opaque
   * materials.
   * @type {!o3d.DrawList}
   */
  this.performanceDrawList = performanceDrawPassInfo.drawList;

  /**
   * The StateSet RenderNode above the ZOrdered DrawPass in this ViewInfo
   * @type {!o3d.StateSet}
   */
  this.zOrderedStateSet = zOrderedDrawPassInfo.stateSet;

  /**
   * The State object used by the zOrderedStateSet object in this ViewInfo.
   * By default AlphaBlendEnable is set to true, SourceBlendFucntion is set to
   * State.BLENDFUNC_SOURCE_ALPHA and DestinationBlendFunction is set to
   * State.BLENDFUNC_INVERSE_SOURCE_ALPHA
   * @type {!o3d.State}
   */
  this.zOrderedState = zOrderedState;

  /**
   * The DrawList used for the zOrdered draw pass. Generally for transparent
   * materials.
   * @type {!o3d.DrawList}
   */
  this.zOrderedDrawList = zOrderedDrawPassInfo.drawList;

  /**
   * The DrawPass used with the performance DrawList created by this ViewInfo.
   * @type {!o3d.DrawPass}
   */
  this.performanceDrawPass = performanceDrawPassInfo.drawPass;

  /**
   * The DrawPass used with the zOrdered DrawList created by this ViewInfo.
   * @type {!o3d.DrawPass}
   */
  this.zOrderedDrawPass = zOrderedDrawPassInfo.drawPass;

  /**
   * A flag whether or not we created the DrawContext for this DrawPassInfo.
   * @private
   * @type {boolean}
   */
  this.ownDrawContext_ = opt_drawContext ? false : true;
};

/**
 * Destroys the various objects created for the view.
 *
 * @param {boolean} opt_destroyDrawContext True if you want view's DrawContext
 *     destroyed. Default = true.
 * @param {boolean} opt_destroyDrawList True if you want view's DrawLists
 *     destroyed. Default = true.
 */
o3djs.rendergraph.ViewInfo.prototype.destroy = function(
    opt_destroyDrawContext,
    opt_destroyDrawList) {
  if (opt_destroyDrawContext === undefined) {
    opt_destroyDrawContext = true;
  }

  for (var ii = 0; ii < this.drawPassInfos_.length; ++ii) {
    this.drawPassInfos_[ii].destroy();
  }

  // Remove everything we created from the pack.
  this.pack.removeObject(this.viewport);
  this.pack.removeObject(this.clearBuffer);
  if (opt_destroyDrawContext && this.ownDrawContext_) {
    this.pack.removeObject(this.drawContext);
  }
  this.pack.removeObject(this.treeTraversal);
  // Remove our substree from its parent.
  this.viewport.parent = null;

  // At this point, IF nothing else is referencing any of these objects
  // they should get removed.
};

/**
 * Creates a draw pass in this ViewInfo.
 *
 * @param {o3d.DrawList.SortMethod} sortMethod How to sort this draw pass's
 *     DrawElements.
 * @param {!o3d.DrawContext} opt_drawContext The DrawContext for this draw pass.
 *     If not passed in the default DrawContext for this ViewInfo will be used.
 * @param {number} opt_priority The priority for this draw pass. If not passed
 *     in the priority will be the next priority for this ViewInfo.
 * @param {!o3d.RenderNode} opt_parent The RenderNode to parent this draw pass
 *     under. If not passed in the draw pass will be parented under the
 *     ViewInfo's viewport RenderNode.
 * @param {!o3d.DrawList} opt_drawList The DrawList for this draw pass. If not
 *     passed in one will be created.
 * @return {!o3djs.rendergraph.DrawPassInfo}
 */
o3djs.rendergraph.ViewInfo.prototype.createDrawPass = function(
    sortMethod,
    opt_drawContext,
    opt_priority,
    opt_parent,
    opt_drawList) {
  opt_drawContext = opt_drawContext || this.drawContext;
  opt_parent = opt_parent || this.viewport;
  opt_priority = (typeof opt_priority !== 'undefined') ? opt_priority :
                 this.priority++;
  var drawPassInfo = o3djs.rendergraph.createDrawPassInfo(
     this.pack,
     opt_drawContext,
     sortMethod,
     opt_parent,
     opt_drawList);
  drawPassInfo.root.priority = opt_priority;
  this.treeTraversal.registerDrawList(
      drawPassInfo.drawList, opt_drawContext, true);

  this.drawPassInfos_.push(drawPassInfo);

  return drawPassInfo;
};

/**
 * Creates a DrawPassInfo to manage a draw pass.
 *
 * @param {!o3d.Pack} pack Pack to manage created objects.
 * @param {!o3d.DrawContext} drawContext The DrawContext for this draw pass.
 * @param {o3d.DrawList.SortMethod} sortMethod How to sort this draw pass's
 *     DrawElements.
 * @param {!o3d.DrawList} opt_drawList The DrawList for this draw pass. If not
 *     passed in one will be created.
 * @param {!o3d.RenderNode} opt_parent The RenderNode to parent this draw pass
 *     under. If not passed the draw pass will not be parented.
 * @return {!o3djs.rendergraph.DrawPassInfo}
 */
o3djs.rendergraph.createDrawPassInfo = function(
    pack,
    drawContext,
    sortMethod,
    opt_parent,
    opt_drawList) {
  return new o3djs.rendergraph.DrawPassInfo(
      pack, drawContext, sortMethod, opt_parent, opt_drawList);
};

/**
 * A class to manage a draw pass.
 * @constructor
 * @param {!o3d.Pack} pack Pack to manage created objects.
 * @param {!o3d.DrawContext} drawContext The DrawContext for this draw pass.
 * @param {o3d.DrawList.SortMethod} sortMethod How to sort this draw pass's
 *     DrawElements.
 * @param {!o3d.DrawList} opt_drawList The DrawList for this draw pass. If not
 *     passed in one will be created.
 * @param {!o3d.RenderNode} opt_parent The RenderNode to parent this draw pass
 *     under. If not passed the draw pass will not be parented.
 * @return {!o3djs.rendergraph.DrawPassInfo}
 */
o3djs.rendergraph.DrawPassInfo = function(pack,
                                          drawContext,
                                          sortMethod,
                                          opt_parent,
                                          opt_drawList) {
  var ownDrawList = opt_drawList ? false : true;

  opt_parent = opt_parent || null;
  opt_drawList = opt_drawList || pack.createObject('DrawList');

  var stateSet = pack.createObject('StateSet');
  var state = pack.createObject('State');
  stateSet.state = state;
  stateSet.parent = opt_parent;

  var drawPass = pack.createObject('DrawPass');
  drawPass.drawList = opt_drawList;
  drawPass.sortMethod = sortMethod;
  drawPass.parent = stateSet;

  /**
   * The pack managing the objects created for this DrawPassInfo.
   * @type {!o3d.Pack}
   */
  this.pack = pack;

  /**
   * The State that affects all things drawn in this DrawPassInfo.
   * @type {!o3d.State}
   */
  this.state = state;

  /**
   * The StateSet that applies the state for this DrawPassInfo.
   * @type {!o3d.StateSet}
   */
  this.stateSet = stateSet;

  /**
   * The DrawPass for this DrawPassInfo.
   * @type {!o3d.DrawPass}
   */
  this.drawPass = drawPass;

  /**
   * The DrawList for this DrawPassInfo.
   * @type {!o3d.DrawList}
   */
  this.drawList = opt_drawList;

  /**
   * The root RenderNode of this DrawPassInfo. This is the RenderNdoe you should
   * use if you want to turn this draw pass off or reparent it.
   * @type {!o3d.RenderNode}
   */
  this.root = stateSet;

  /**
   * A flag whether or not we created the DrawList for this DrawPassInfo.
   * @private
   * @type {boolean}
   */
  this.ownDrawList_ = ownDrawList;
};

/**
 * Frees the resources created for this DrawPassInfo.
 */
o3djs.rendergraph.DrawPassInfo.prototype.destroy = function() {
  // Remove everything we created from the pack.
  if (this.ownDrawList_) {
    this.drawPass.drawList = null;
    this.pack.removeObject(this.drawList);
  }
  this.drawPass.parent = null;
  this.stateSet.parent = null;
  this.pack.removeObject(this.drawPass);
  this.pack.removeObject(this.stateSet);
  this.pack.removeObject(this.state);
};

/*
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview This file contains a basic utility library that simplifies the
 * creation of simple 2D Canvas surfaces for the purposes of drawing 2D elements
 * in O3D.
 *
 * Example
 *
 * <pre>
 * &lt;html&gt;&lt;body&gt;
 * &lt;script type="text/javascript" src="o3djs/all.js"&gt;
 * &lt;/script&gt;
 * &lt;script&gt;
 * window.onload = init;
 *
 * function init() {
 *   o3djs.base.makeClients(initStep2);
 * }
 *
 * function initStep2(clientElements) {
 *   var clientElement = clientElements[0];
 *   var client = clientElement.client;
 *   var pack = client.createPack();
 *   var viewInfo = o3djs.rendergraph.createBasicView(
 *       pack,
 *       client.root,
 *       client.renderGraphRoot);
 *
 *   // Create an instance of the canvas utility library.
 *   var canvasLib = o3djs.canvas.create(
 *       pack, client.root, g_viewInfo);
 *
 *   // Create a 700x500 rectangle at (x,y,z) = (4, 10, 0)
 *   var canvasQuad = canvasLib.createXYQuad(4, 10, 0, 700, 500, false);
 *
 *   // Draw into the canvas.
 *   canvasQuad.canvas.clear([1, 0, 0, 1]);
 *   canvasQuad.canvas.drawText('Hello', 0, 10, canvasPaint);
 *   ...
 *   ...
 *
 *   // Update the o3d texture associated with the canvas.
 *   canvasQuad.updateTexture();
 * }
 * &lt;/script&gt;
 * &lt;div id="o3d" style="width: 600px; height: 600px"&gt;&lt;/div&gt;
 * &lt;/body&gt;&lt;/html&gt;
 * </pre>
 *
 */

o3djs.provide('o3djs.canvas');

/**
 * A Module for using a 2d canvas.
 * @namespace
 */
o3djs.canvas = o3djs.canvas || {};

/**
 * Creates an o3djs.canvas library object through which CanvasQuad objects
 * can be created.
 * @param {!o3d.Pack} pack to manage objects created by this library.
 * @param {!o3d.Transform} root Default root for visual objects.
 * @param {!o3djs.rendergraph.ViewInfo} viewInfo A ViewInfo object as
 *  created by o3djs.createView which contains draw lists that the created
 *  quads will be placed into.
 * @return {!o3djs.canvas.CanvasInfo} A CanvasInfo object containing
 *         references to all the common O3D elements used by this instance
 *         of the library.
 */
o3djs.canvas.create = function(pack, root, viewInfo) {
  return new o3djs.canvas.CanvasInfo(pack, root, viewInfo);
};

/**
 * The shader code used by the canvas quads.  It only does two things:
 *   1. Transforms the shape to screen space via the worldViewProjection matrix.
 *   2. Performs a texture lookup to display the contents of the texture
 *      bound to texSampler0.
 * @type {string}
 */
o3djs.canvas.buildShaderString = function() {
  var p = o3djs.effect;
  var varyingDecls = p.BEGIN_OUT_STRUCT +
      p.VARYING + p.FLOAT4 + ' ' +
      p.VARYING_DECLARATION_PREFIX + 'position' +
      p.semanticSuffix('POSITION') + ';\n' +
      p.VARYING + p.FLOAT2 + ' ' +
      p.VARYING_DECLARATION_PREFIX + 'texCoord' +
      p.semanticSuffix('TEXCOORD0') + ';\n' +
      p.END_STRUCT;

  return 'uniform ' + p.MATRIX4 + ' worldViewProjection' +
      p.semanticSuffix('WORLDVIEWPROJECTION') + ';\n\n' +
      p.BEGIN_IN_STRUCT +
      p.ATTRIBUTE + p.FLOAT4 + ' position' +
      p.semanticSuffix('POSITION') + ';\n' +
      p.ATTRIBUTE + p.FLOAT2 + ' texCoord0' +
      p.semanticSuffix('TEXCOORD0') + ';\n' +
      p.END_STRUCT +
      '\n' +
      varyingDecls +
      '\n' +
      p.beginVertexShaderMain() +
      '  ' + p.VERTEX_VARYING_PREFIX + 'position = ' +
      p.mul(p.ATTRIBUTE_PREFIX + 'position',
          'worldViewProjection') + ';\n' +
      '  ' + p.VERTEX_VARYING_PREFIX + 'texCoord = ' +
      p.ATTRIBUTE_PREFIX + 'texCoord0;\n' +
      p.endVertexShaderMain() +
      '\n' +
      p.pixelShaderHeader() +
      'uniform ' + p.SAMPLER + ' texSampler0;\n' +
      p.repeatVaryingDecls(varyingDecls) +
      p.beginPixelShaderMain() +
      p.endPixelShaderMain(p.TEXTURE + '2D' +
          '(texSampler0, ' + p.PIXEL_VARYING_PREFIX + 'texCoord)') +
      p.entryPoints() +
      p.matrixLoadOrder();
};


/**
 * The CanvasInfo object creates and keeps references to the O3D objects
 * that are shared between all CanvasQuad objects created through it.
 * @constructor
 * @param {!o3d.Pack} pack Pack to manage CanvasInfo objects.
 * @param {!o3d.Transform} root Default root for visual objects.
 * @param {!o3djs.rendergraph.ViewInfo} viewInfo A ViewInfo object as
 *     created by o3djs.createView which contains draw lists that the
 *     created quads will be placed into.
 */
o3djs.canvas.CanvasInfo = function(pack, root, viewInfo) {
  /**
   * The pack being used to manage objects created by this CanvasInfo.
   * @type {!o3d.Pack}
   */
  this.pack = pack;

  /**
   * The ViewInfo this CanvasInfo uses for rendering.
   * @type {!o3djs.rendergraph.ViewInfo}
   */
  this.viewInfo = viewInfo;

  /**
   * The default root for objects created by this CanvasInfo.
   * @type {!o3d.Transform}
   */
  this.root = root;

  /**
   * The Effect object shared by all CanvasQuad instances.
   * @type {!o3d.Effect}
   */
  this.effect_ = this.pack.createObject('Effect');
  this.effect_.loadFromFXString(o3djs.canvas.buildShaderString());

  /**
   * Material for canvases with transparent content
   * @type {!o3d.Material}
   */
  this.transparentMaterial_ = this.pack.createObject('Material');

  /**
   * Material for canvases with opaque content.
   * @type {!o3d.Material}
   */
  this.opaqueMaterial_ = this.pack.createObject('Material');

  this.transparentMaterial_.effect = this.effect_;
  this.opaqueMaterial_.effect = this.effect_;

  this.transparentMaterial_.drawList = viewInfo.zOrderedDrawList;
  this.opaqueMaterial_.drawList = viewInfo.performanceDrawList;

  /**
   * State object to handle the transparency blending mode
   * for transparent canvas quads.
   * The canvas bitmap already multiplies the color values by alpha.  In order
   * to avoid a black halo around text drawn on a transparent background we
   * need to set the blending mode as follows.
   * @type {!o3d.State}
   */
  this.transparentState_ = this.pack.createObject('State');
  this.transparentState_.getStateParam('AlphaBlendEnable').value = true;
  this.transparentState_.getStateParam('SourceBlendFunction').value =
      o3djs.base.o3d.State.BLENDFUNC_ONE;
  this.transparentState_.getStateParam('DestinationBlendFunction').value =
      o3djs.base.o3d.State.BLENDFUNC_INVERSE_SOURCE_ALPHA;

  this.transparentMaterial_.state = this.transparentState_;

  // Create 2d plane shapes. createPlane makes an XZ plane by default
  // so we pass in matrix to rotate it to an XY plane. We could do
  // all our manipulations in XZ but most people seem to like XY for 2D.

  /**
   * A shape for transparent quads.
   * @type {!o3d.Shape}
   */
  this.transparentQuadShape = o3djs.primitives.createPlane(
      this.pack,
      this.transparentMaterial_,
      1,
      1,
      1,
      1,
      [[1, 0, 0, 0],
       [0, 0, 1, 0],
       [0, -1, 0, 0],
       [0, 0, 0, 1]]);

  /**
   * A shape for opaque quads.
   * @type {!o3d.Shape}
   */
  this.opaqueQuadShape = o3djs.primitives.createPlane(
      this.pack,
      this.opaqueMaterial_,
      1,
      1,
      1,
      1,
      [[1, 0, 0, 0],
       [0, 0, 1, 0],
       [0, -1, 0, 0],
       [0, 0, 0, 1]]);
};
/**
 * The CanvasQuad object encapsulates a Transform, a rectangle Shape,
 * an effect that applies a texture to render the quad, and a matching Canvas
 * object that can render into the texture.  The dimensions of the texture and
 * the canvas object match those of the quad in order to get pixel-accurate
 * results with the appropriate orthographic projection.
 * The resulting rectangle Shape is positioned at the origin.  It can be moved
 * around by setting the localMatrix on the Transform object referenced to by
 * the canvasQuad.transform property.
 * The Canvas associated with the returned CanvasQuad object can be retrieved
 * from the object's 'canvas' property.  After issuing any draw commands on the
 * Canvas, you need to call the updateTexture() method on the CanvasQuad to
 * update the contents of the quad surface.
 * @constructor
 * @param {!o3djs.canvas.CanvasInfo} canvasInfo The CanvasInfo object
 *     instance creating this CanvasQuad.
 * @param {number} width The width of the quad.
 * @param {number} height The height of the quad.
 * @param {boolean} transparent Set to true if the canvas will
 *     be transparent so that the appropriate blending modes are set.
 * @param {!o3d.Transform} opt_parent parent transform to parent
 *     the newly created quad under. If no parent transform is provided then
 *     the quad gets parented under the CanvasInfo's root.
 */
o3djs.canvas.CanvasQuad = function(canvasInfo,
                                   width,
                                   height,
                                   transparent,
                                   opt_parent) {
  /**
   * The CanvasInfo managing this CanvasQuad
   * @type {!o3djs.canvas.CanvasInfo}
   */
  this.canvasInfo = canvasInfo;
  var parentTransform = opt_parent || canvasInfo.root;

  // create a transform for positioning

  /**
   * A transform for this quad.
   * @type {!o3d.Transform}
   */
  this.transform = canvasInfo.pack.createObject('Transform');
  this.transform.parent = parentTransform;

  // create a transform for scaling to the size of the image just so
  // we don't have to manage that manually in the transform above.

  /**
   * A scale transform for this quad.
   * You can change the scale the quad without effecting its positon using
   * this transform.
   * @type {!o3d.Transform}
   */
  this.scaleTransform = canvasInfo.pack.createObject('Transform');
  this.scaleTransform.parent = this.transform;

  /**
   * The texture the canvas will draw on.
   * @type {!o3d.Texture2D}
   */
  this.texture = /** @type {!o3d.Texture2D} */ (canvasInfo.pack.createTexture2D(
      width,
      height,
      o3djs.base.o3d.Texture.ARGB8,
      1, // mipmap levels
      false));

  // Create a Canvas object to go with the quad.

  /**
   * The Canvas object used to draw on this quad.
   * @type {!o3d.Canvas}
   */
  this.canvas = canvasInfo.pack.createObject('Canvas');
  this.canvas.setSize(width, height);

  /**
   * The sampler for the texture.
   * @type {!o3d.Sampler}
   */
  this.sampler = canvasInfo.pack.createObject('Sampler');
  this.sampler.addressModeU = o3djs.base.o3d.Sampler.CLAMP;
  this.sampler.addressModeV = o3djs.base.o3d.Sampler.CLAMP;

  /**
   * The param sampler for this transform.
   * @private
   * @type {!o3d.ParamSampler}
   */
  this.paramSampler_ = this.scaleTransform.createParam('texSampler0',
                                                       'ParamSampler');
  this.paramSampler_.value = this.sampler;

  this.sampler.texture = this.texture;
  if (transparent) {
    this.scaleTransform.addShape(canvasInfo.transparentQuadShape);
  } else {
    this.scaleTransform.addShape(canvasInfo.opaqueQuadShape);
  }
  this.scaleTransform.translate(width / 2, height / 2, 0);
  this.scaleTransform.scale(width, -height, 1);
};

/**
 * Copies the current contents of the Canvas object to the texture associated
 * with the quad.  This method should be called after any new draw calls have
 * been issued to the CanvasQuad's Canvas object.
 */
o3djs.canvas.CanvasQuad.prototype.updateTexture = function() {
  var width = this.texture.width;
  var height = this.texture.height;
  this.texture.drawImage(this.canvas, 0, height - 1, width, -height,
                         0, 0, 0, width, height);
};

/**
 * Creates a CanvasQuad object on the XY plane at the specified position.
 * @param {number} topX The x coordinate of the top left corner of the quad.
 * @param {number} topY The y coordinate of the top left corner of the quad.
 * @param {number} z The z coordinate of the quad.  z values are negative
 *     numbers, the smaller the number the further back the quad will be.
 * @param {number} width The width of the quad.
 * @param {number} height The height of the quad.
 * @param {boolean} transparent Set to true if the canvas bitmap uses
 *     transparency so that the appropriate blending modes are set.
 * @param {!o3d.Transform} opt_parent parent transform to parent the newly
 *     created quad under.  If no parent transform is provided then the quad
 *     gets parented under the CanvasInfo's root.
 * @return {!o3djs.canvas.CanvasQuad} The newly created CanvasQuad object.
 */
o3djs.canvas.CanvasInfo.prototype.createXYQuad = function(topX,
                                                          topY,
                                                          z,
                                                          width,
                                                          height,
                                                          transparent,
                                                          opt_parent) {
  var canvasQuad = new o3djs.canvas.CanvasQuad(this,
                                               width,
                                               height,
                                               transparent,
                                               opt_parent);

  canvasQuad.transform.translate(topX, topY, z);
  return canvasQuad;
};

/**
 * Creates a CanvasQuad object of the given size. The resulting rectangle Shape
 * is centered at the origin.  It can be moved around by setting the
 * localMatrix on the Transform object referenced to by the canvasQuad.transform
 * property.
 * @param {number} width The width of the quad.
 * @param {number} height The height of the quad.
 * @param {boolean} transparent Set to true if the canvas bitmap uses
 *     transparency so that the appropriate blending modes are set.
 * @param {!o3d.Transform} opt_parent parent transform to parent the newly
 *     created quad under.  If no parent transform is provided then the quad
 *     gets parented under the CanvasInfo's root.
 * @return {!o3djs.canvas.CanvasQuad} The newly created CanvasQuad object.
 */
o3djs.canvas.CanvasInfo.prototype.createQuad = function(width,
                                                        height,
                                                        transparent,
                                                        opt_parent) {
  return new o3djs.canvas.CanvasQuad(this,
                                     width,
                                     height,
                                     transparent,
                                     opt_parent);
};


/*
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview This file contains various functions for helping setup
 * materials for o3d.  It puts them in the "material" module on the
 * o3djs object.
 *
 *     Note: This library is only a sample. It is not meant to be some official
 *     library. It is provided only as example code.
 *
 */

o3djs.provide('o3djs.material');

/**
 * A Module for materials.
 * @namespace
 */
o3djs.material = o3djs.material || {};

/**
 * Checks a material's params by name to see if it possibly has non 1.0 alpha.
 * Given a name, checks for a ParamTexture called 'nameTexture' and if that
 * fails, checks for a ParamFloat4 'name'.
 * @private
 * @param {!o3d.Material} material Materal to check.
 * @param {string} name name of color params to check.
 * @return {{found: boolean, nonOneAlpha: boolean}} found is true if one of
 *     the params was found, nonOneAlpha is true if that param had non 1.0
 *     alpha.
 */
o3djs.material.hasNonOneAlpha_ = function(material, name) {
  var found = false;
  var nonOneAlpha = false;
  var texture = null;
  var samplerParam = material.getParam(name + 'Sampler');
  if (samplerParam && samplerParam.isAClassName('o3d.ParamSampler')) {
    found = true;
    var sampler = samplerParam.value;
    if (sampler) {
      texture = sampler.texture;
    }
  } else {
    var textureParam = material.getParam(name + 'Texture');
    if (textureParam && textureParam.isAClassName('o3d.ParamTexture')) {
      found = true;
      texture = textureParam.value;
    }
  }

  if (texture && !texture.alphaIsOne) {
    nonOneAlpha = true;
  }

  if (!found) {
    var colorParam = material.getParam(name);
    if (colorParam && colorParam.isAClassName('o3d.ParamFloat4')) {
      found = true;
      // TODO: this check does not work. We need to check for the
      // <transparency> and <transparent> elements or something.
      // if (colorParam.value[3] < 1) {
      //   nonOneAlpha = true;
      // }
    }
  }
  return {found: found, nonOneAlpha: nonOneAlpha};
};

/**
 * Prepares a material by setting their drawList and possibly creating
 * an standard effect if one does not already exist.
 *
 * This function is very specific to our sample importer. It expects that if
 * no Effect exists on a material that certain extra Params have been created
 * on the Material to give us instructions on what to Effects to create.
 *
 * @param {!o3d.Pack} pack Pack to manage created objects.
 * @param {!o3djs.rendergraph.ViewInfo} viewInfo as returned from
 *     o3djs.rendergraph.createView.
 * @param {!o3d.Material} material to prepare.
 * @param {string} opt_effectType type of effect to create ('phong',
 *     'lambert', 'constant').
 *
 * @see o3djs.material.attachStandardEffect
 */
o3djs.material.prepareMaterial = function(pack,
                                          viewInfo,
                                          material,
                                          opt_effectType) {
  // Assume we want the performance list
  var drawList = viewInfo.performanceDrawList;
  // First check if we have a tag telling us that it is or is not
  // transparent
  if (!material.drawList) {
    var param = material.getParam('collada.transparent');
    if (param && param.className == 'o3d.ParamBoolean') {
      material.drawList = param.value ? viewInfo.zOrderedDrawList :
                                        viewInfo.performanceDrawList;
    }
  }
  // If the material has no effect, try to build shaders for it.
  if (!material.effect) {
    // If the user didn't pass an effect type in see if one was stored there
    // by our importer.
    if (!opt_effectType) {
      // Retrieve the lightingType parameter from the material, if any.
      var lightingType = o3djs.effect.getColladaLightingType(material);
      if (lightingType) {
        opt_effectType = lightingType;
      }
    }
    if (opt_effectType) {
      o3djs.material.attachStandardEffect(pack,
                                          material,
                                          viewInfo,
                                          opt_effectType);
      // For collada common profile stuff guess what drawList to use. Note: We
      // can only do this for collada common profile stuff because we supply
      // the shaders and therefore now the inputs and how they are used.
      // For other shaders you've got to do this stuff yourself. On top of
      // that this is a total guess. Just because a texture has no alpha
      // it does not follow that you don't want it in the zOrderedDrawList.
      // That is application specific. Here we are just making a guess and
      // hoping that it covers most cases.
      if (material.drawList == null) {
        // Check the common profile params.
        var result = o3djs.material.hasNonOneAlpha_(material, 'diffuse');
        if (!result.found) {
          result = o3djs.material.hasNonOneAlpha_(material, 'emissive');
        }
        if (result.nonOneAlpha) {
          drawList = viewInfo.zOrderedDrawList;
        }
      }
    }
  }
  if (!material.drawList) {
    material.drawList = drawList;
  }
};

/**
 * Prepares all the materials in the given pack by setting their drawList and
 * if they don't have an Effect, creating one for them.
 *
 * This function is very specific to our sample importer. It expects that if
 * no Effect exists on a material that certain extra Params have been created
 * on the Material to give us instructions on what to Effects to create.
 *
 * @param {!o3d.Pack} pack Pack to prepare.
 * @param {!o3djs.rendergraph.ViewInfo} viewInfo as returned from
 *     o3djs.rendergraph.createView.
 * @param {!o3d.Pack} opt_effectPack Pack to create effects in. If this
 *     is not specifed the pack to prepare above will be used.
 *
 * @see o3djs.material.prepareMaterial
 */
o3djs.material.prepareMaterials = function(pack,
                                           viewInfo,
                                           opt_effectPack) {
  var materials = pack.getObjectsByClassName('o3d.Material');
  for (var mm = 0; mm < materials.length; mm++) {
    o3djs.material.prepareMaterial(opt_effectPack || pack,
                                   viewInfo,
                                   materials[mm]);
  }
};

/**
 * Builds a standard effect for a given material.
 * If the material already has an effect, none is created.
 * @param {!o3d.Pack} pack Pack to manage created objects.
 * @param {!o3d.Material} material The material for which to create an
 *     effect.
 * @param {string} effectType Type of effect to create ('phong', 'lambert',
 *     'constant').
 *
 * @see o3djs.effect.attachStandardShader
 */
o3djs.material.attachStandardEffectEx = function(pack,
                                                 material,
                                                 effectType) {
  if (!material.effect) {
    if (!o3djs.effect.attachStandardShader(pack,
                                           material,
                                           [0, 0, 0],
                                           effectType)) {
      throw 'Could not attach a standard effect';
    }
  }
};

/**
 * Builds a standard effect for a given material.  The position of the
 * default light is set to the view position.  If the material already has
 * an effect, none is created.
 * @param {!o3d.Pack} pack Pack to manage created objects.
 * @param {!o3d.Material} material The material for which to create an
 *     effect.
 * @param {!o3djs.rendergraph.ViewInfo} viewInfo as returned from
 *     o3djs.rendergraph.createView.
 * @param {string} effectType Type of effect to create ('phong', 'lambert',
 *     'constant').
 *
 * @see o3djs.effect.attachStandardShader
 */
o3djs.material.attachStandardEffect = function(pack,
                                               material,
                                               viewInfo,
                                               effectType) {
  if (!material.effect) {
    var lightPos =
        o3djs.math.matrix4.getTranslation(
            o3djs.math.inverse(viewInfo.drawContext.view));
    if (!o3djs.effect.attachStandardShader(pack,
                                           material,
                                           lightPos,  // TODO(gman): remove this
                                           effectType)) {
      throw 'Could not attach a standard effect';
    }
  }
};

/**
 * Prepares all the materials in the given pack by setting their
 * drawList.
 * @param {!o3d.Pack} pack Pack to manage created objects.
 * @param {!o3d.DrawList} drawList DrawList to assign to materials.
 */
o3djs.material.setDrawListOnMaterials = function(pack, drawList) {
  var materials = pack.getObjectsByClassName('o3d.Material');
  for (var mm = 0; mm < materials.length; mm++) {
    var material = materials[mm];
    // TODO: look at flags on the material left by the importer
    //   to decide which draw list to use.
    material.drawList = drawList;
  }
};

/**
 * This function creates a basic material for when you just want to get
 * something on the screen quickly without having to manually setup shaders.
 * You can call this function something like.
 *
 * <pre>
 * &lt;html&gt;&lt;body&gt;
 * &lt;script type="text/javascript" src="o3djs/all.js"&gt;
 * &lt;/script&gt;
 * &lt;script&gt;
 * window.onload = init;
 *
 * function init() {
 *   o3djs.base.makeClients(initStep2);
 * }
 *
 * function initStep2(clientElements) {
 *   var clientElement = clientElements[0];
 *   var client = clientElement.client;
 *   var pack = client.createPack();
 *   var viewInfo = o3djs.rendergraph.createBasicView(
 *       pack,
 *       client.root,
 *       client.renderGraphRoot);
 *   var material = o3djs.material.createBasicMaterial(
 *       pack,
 *       viewInfo,
 *       [1, 0, 0, 1]);  // red
 *   var shape = o3djs.primitives.createCube(pack, material, 10);
 *   var transform = pack.createObject('Transform');
 *   transform.parent = client.root;
 *   transform.addShape(shape);
 *   o3djs.camera.fitContextToScene(client.root,
 *                                  client.width,
 *                                  client.height,
 *                                  viewInfo.drawContext);
 * }
 * &lt;/script&gt;
 * &lt;div id="o3d" style="width: 600px; height: 600px"&gt;&lt;/div&gt;
 * &lt;/body&gt;&lt;/html&gt;
 * </pre>
 *
 * @param {!o3d.Pack} pack Pack to manage created objects.
 * @param {!o3djs.rendergraph.ViewInfo} viewInfo as returned from
 *     o3djs.rendergraph.createBasicView.
 * @param {(!o3djs.math.Vector4|!o3d.Texture)} colorOrTexture Either a color in
 *     the format [r, g, b, a] or an O3D texture.
 * @param {boolean} opt_transparent Whether or not the material is transparent.
 *     Defaults to false.
 * @return {!o3d.Material} The created material.
 */
o3djs.material.createBasicMaterial = function(pack,
                                              viewInfo,
                                              colorOrTexture,
                                              opt_transparent) {
  var material = pack.createObject('Material');
  material.drawList = opt_transparent ? viewInfo.zOrderedDrawList :
                                        viewInfo.performanceDrawList;

  // If it has a length assume it's a color, otherwise assume it's a texture.
  if (colorOrTexture.length) {
    material.createParam('diffuse', 'ParamFloat4').value = colorOrTexture;
  } else {
    var paramSampler = material.createParam('diffuseSampler', 'ParamSampler');
    var sampler = pack.createObject('Sampler');
    paramSampler.value = sampler;
    sampler.texture = colorOrTexture;
  }

  // Create some suitable defaults for the material to save the user having
  // to know all this stuff right off the bat.
  material.createParam('emissive', 'ParamFloat4').value = [0, 0, 0, 1];
  material.createParam('ambient', 'ParamFloat4').value = [0, 0, 0, 1];
  material.createParam('specular', 'ParamFloat4').value = [1, 1, 1, 1];
  material.createParam('shininess', 'ParamFloat').value = 50;
  material.createParam('specularFactor', 'ParamFloat').value = 1;
  material.createParam('lightColor', 'ParamFloat4').value = [1, 1, 1, 1];
  var lightPositionParam = material.createParam('lightWorldPos',
                                                'ParamFloat3');

  o3djs.material.attachStandardEffect(pack, material, viewInfo, 'phong');

  // We have to set the light position after calling attachStandardEffect
  // because attachStandardEffect sets it based on the view.
  lightPositionParam.value = [1000, 2000, 3000];

  return material;
};

/**
 * This function creates a constant material. No lighting. It is especially
 * useful for debugging shapes and 2d UI elements.
 *
 * @param {!o3d.Pack} pack Pack to manage created objects.
 * @param {!o3d.DrawList} drawList The DrawList for the material.
 * @param {(!o3djs.math.Vector4|!o3d.Texture)} colorOrTexture Either a color in
 *     the format [r, g, b, a] or an O3D texture.
 * @return {!o3d.Material} The created material.
 */
o3djs.material.createConstantMaterialEx = function(pack,
                                                   drawList,
                                                   colorOrTexture) {
  var material = pack.createObject('Material');
  material.drawList = drawList;

  // If it has a length assume it's a color, otherwise assume it's a texture.
  if (colorOrTexture.length) {
    material.createParam('emissive', 'ParamFloat4').value = colorOrTexture;
  } else {
    var paramSampler = material.createParam('emissiveSampler', 'ParamSampler');
    var sampler = pack.createObject('Sampler');
    paramSampler.value = sampler;
    sampler.texture = colorOrTexture;
  }

  o3djs.material.attachStandardEffectEx(pack, material, 'constant');

  return material;
};

/**
 * This function creates a constant material. No lighting. It is especially
 * useful for debugging shapes and 2d UI elements.
 *
 * @param {!o3d.Pack} pack Pack to manage created objects.
 * @param {!o3djs.rendergraph.ViewInfo} viewInfo as returned from
 *     o3djs.rendergraph.createBasicView.
 * @param {(!o3djs.math.Vector4|!o3d.Texture)} colorOrTexture Either a color in
 *     the format [r, g, b, a] or an O3D texture.
 * @param {boolean} opt_transparent Whether or not the material is transparent.
 *     Defaults to false.
 * @return {!o3d.Material} The created material.
 */
o3djs.material.createConstantMaterial = function(pack,
                                                 viewInfo,
                                                 colorOrTexture,
                                                 opt_transparent) {
  return o3djs.material.createConstantMaterialEx(
      pack,
      opt_transparent ? viewInfo.zOrderedDrawList :
                        viewInfo.performanceDrawList,
      colorOrTexture)
};

/**
 * This function creates 2 color procedureal texture material.
 *
 * @see o3djs.material.createBasicMaterial
 *
 * @param {!o3d.Pack} pack Pack to manage created objects.
 * @param {!o3djs.rendergraph.ViewInfo} viewInfo as returned from
 *     o3djs.rendergraph.createBasicView.
 * @param {!o3djs.math.Vector4} opt_color1 a color in the format [r, g, b, a].
 *     Defaults to a medium blue-green.
 * @param {!o3djs.math.Vector4} opt_color2 a color in the format [r, g, b, a].
 *     Defaults to a light blue-green.
 * @param {boolean} opt_transparent Whether or not the material is transparent.
 *     Defaults to false.
 * @param {number} opt_checkSize Defaults to 10 units.
 * @return {!o3d.Material} The created material.
 */
o3djs.material.createCheckerMaterial = function(pack,
                                                viewInfo,
                                                opt_color1,
                                                opt_color2,
                                                opt_transparent,
                                                opt_checkSize) {
  opt_color1 = opt_color1 || [0.4, 0.5, 0.5, 1];
  opt_color2 = opt_color2 || [0.6, 0.8, 0.8, 1];
  opt_checkSize = opt_checkSize || 10;

  var effect = o3djs.effect.createCheckerEffect(pack);
  var material = pack.createObject('Material');
  material.effect = effect;
  material.drawList = opt_transparent ? viewInfo.zOrderedDrawList :
                                        viewInfo.performanceDrawList;
  o3djs.effect.createUniformParameters(pack, effect, material);

  material.getParam('color1').value = opt_color1;
  material.getParam('color2').value = opt_color2;
  material.getParam('checkSize').value = opt_checkSize;

  return material;
};

/**
 * Creates a material for an effect loaded from a file.
 * If the effect has already been loaded in the pack it will be reused.
 * @param {!o3d.Pack} pack Pack to create effect in.
 * @param {string} url Url for effect file.
 * @param {!o3d.DrawList} drawList DrawList to assign effect to.
 * @return {!o3d.Material} The material.
 */
o3djs.material.createMaterialFromFile = function(pack, url, drawList) {
  var effect = o3djs.effect.createEffectFromFile(pack, url);

  var material = pack.createObject('Material');
  material.effect = effect;
  material.drawList = drawList;
  o3djs.effect.createUniformParameters(pack, effect, material);

  return material;
};

/**
 * Binds params to all materials in a pack by name.
 * @param {!o3d.Material} material Material to bind params on.
 * @param {!Object} params A object where each property is the name of a param
 *     and its value is that param.
 */
o3djs.material.bindParamsOnMaterial = function(material, params) {
  for (var paramName in params) {
    var sourceParam = params[paramName];
    var param = material.getParam(paramName);
    if (param && sourceParam.isAClassName(param.className)) {
      param.bind(sourceParam);
    }
  }
};

/**
 * Binds params to all materials in a pack by name.
 * @param {!o3d.Pack} pack Pack with materials to bind.
 * @param {!Object} params A object where each property is the name of a param
 *     and its value is that param.
 */
o3djs.material.bindParams = function(pack, params) {
  var materials = pack.getObjectsByClassName('o3d.Material');
  for (var mm = 0; mm < materials.length; ++mm) {
    o3djs.material.bindParamsOnMaterial(materials[mm], params);
  }
};

/**
 * Creates params from a param spec.
 * @param {!o3d.Pack} pack Pack to create params in.
 * @param {!Object} paramSpec An object where each property is the name of a
 *     param and its value is the type of param.
 * @return {!Object} params A object where each property is the name of a param
 *     and its value is that param.
 */
o3djs.material.createParams = function(pack, paramSpec) {
  var paramObject = pack.createObject('ParamObject');
  var params = { };
  for (var paramName in paramSpec) {
    params[paramName] = paramObject.createParam(paramName,
                                                paramSpec[paramName]);
  }
  return params;
};

/**
 * Creates the global params need by the shaders built in effect.js
 *
 * The params currently created are 'lightColor' which is a ParamFloat4 and
 * 'lightWorldPos' which is a ParamFloat3. You can set their values like this
 *
 * <pre>
 * var params = o3djs.material.createStandardParams(pack);
 * param.lightColor.value = [1, 0, 0, 1];  // red
 * param.lightWorldPos.value = [1000, 2000, 3000];  // set light position.
 * </pre>
 *
 * Note: This function just creates the params. It does not connect them to
 * anything. See o3djs.material.createAndBindStandardParams,
 * o3djs.material.createParams and o3djs.material.bindParams
 *
 * @see o3djs.material.createAndBindStandardParams
 * @see o3djs.material.createParams
 * @see o3djs.material.bindParams
 *
 * @param {!o3d.Pack} pack Pack to create params in.
 * @return {!Object} params A object where each property is the name of a param
 *     and its value is that param.
 */
o3djs.material.createStandardParams = function(pack) {
  var paramSpec = {
    'lightColor': 'ParamFloat4',
    'lightWorldPos': 'ParamFloat3'};
  return o3djs.material.createParams(pack, paramSpec);
};

/**
 * Creates the global params need by the shaders built in effect.js then binds
 * all the matching params on materials in pack to these global params.
 *
 * The params currently created are 'lightColor' which is a ParamFloat4 and
 * 'lightWorldPos' which is a ParamFloat3. You can set their values like this
 *
 * <pre>
 * var params = o3djs.material.createAndBindStandardParams(pack);
 * param.lightColor.value = [1, 0, 0, 1];  // red
 * param.lightWorldPos.value = [1000, 2000, 3000];  // set light position.
 * </pre>
 *
 * @see o3djs.material.createParams
 * @see o3djs.material.bindParams
 *
 * @param {!o3d.Pack} pack Pack to create params in.
 * @return {!Object} params A object where each property is the name of a param
 *     and its value is that param.
 */
o3djs.material.createAndBindStandardParams = function(pack) {
  var params = o3djs.material.createStandardParams(pack);
  o3djs.material.bindParams(pack, params);
  return params;
};

/*
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview This file contains various functions and class for io.
 */

o3djs.provide('o3djs.io');

/**
 * A Module with various io functions and classes.
 * @namespace
 */
o3djs.io = o3djs.io || {};

/**
 * Creates a LoadInfo object.
 * @param {(!o3d.ArchiveRequest|!o3d.FileRequest|!XMLHttpRequest)} opt_request
 *     The request to watch.
 * @param {boolean} opt_hasStatus true if opt_request is a
 *     o3d.ArchiveRequest vs for example an o3d.FileRequest or an
 *     XMLHttpRequest.
 * @return {!o3djs.io.LoadInfo} The new LoadInfo.
 * @see o3djs.io.LoadInfo
 */
o3djs.io.createLoadInfo = function(opt_request, opt_hasStatus) {
  return new o3djs.io.LoadInfo(opt_request, opt_hasStatus);
};

/**
 * A class to help with progress reporting for most loading utilities.
 *
 * Example:
 * <pre>
 * var g_loadInfo = null;
 * g_id = window.setInterval(statusUpdate, 500);
 * g_loadInfo = o3djs.scene.loadScene(client, pack, parent,
 *                                    'http://google.com/somescene.o3dtgz',
 *                                    callback);
 *
 * function callback(pack, parent, exception) {
 *   g_loadInfo = null;
 *   window.clearInterval(g_id);
 *   if (!exception) {
 *     // do something with scene just loaded
 *   }
 * }
 *
 * function statusUpdate() {
 *   if (g_loadInfo) {
 *     var progress = g_loadInfo.getKnownProgressInfoSoFar();
 *     document.getElementById('loadstatus').innerHTML = progress.percent;
 *   }
 * }
 * </pre>
 *
 * @constructor
 * @param {(!o3d.ArchiveRequest|!o3d.FileRequest|!XMLHttpRequest)} opt_request
 *     The request to watch.
 * @param {boolean} opt_hasStatus true if opt_request is a
 *     o3d.ArchiveRequest vs for example an o3d.FileRequest or an
 *     XMLHttpRequest.
 * @see o3djs.scene.loadScene
 * @see o3djs.io.loadArchive
 * @see o3djs.io.loadTexture
 * @see o3djs.loader.Loader
 */
o3djs.io.LoadInfo = function(opt_request, opt_hasStatus) {
  this.request_ = opt_request;
  this.hasStatus_ = opt_hasStatus;
  this.streamLength_ = 0;  // because the request may have been freed.
  this.children_ = [];
};

/**
 * Adds another LoadInfo as a child of this LoadInfo so they can be
 * managed as a group.
 * @param {!o3djs.io.LoadInfo} loadInfo The child LoadInfo.
 */
o3djs.io.LoadInfo.prototype.addChild = function(loadInfo) {
  this.children_.push(loadInfo);
};

/**
 * Marks this LoadInfo as finished.
 */
o3djs.io.LoadInfo.prototype.finish = function() {
  if (this.request_) {
    if (this.hasStatus_) {
      this.streamLength_ = this.request_.streamLength;
    }
    this.request_ = null;
  }
};

/**
 * Gets the total bytes that will be streamed known so far.
 * If you are only streaming 1 file then this will be the info for that file but
 * if you have queued up many files using an o3djs.loader.Loader only a couple of
 * files are streamed at a time meaning that the size is not known for files
 * that have yet started to download.
 *
 * If you are downloading many files for your application and you want to
 * provide a progress status you have about 4 options
 *
 * 1) Use LoadInfo.getTotalBytesDownloaded() /
 * LoadInfo.getTotalKnownBytesToStreamSoFar() and just be aware the bar will
 * grown and then shrink as new files start to download and their lengths
 * become known.
 *
 * 2) Use LoadInfo.getTotalRequestsDownloaded() /
 * LoadInfo.getTotalKnownRequestsToStreamSoFar() and be aware the granularity
 * is not all that great since it only reports fully downloaded files. If you
 * are downloading a bunch of small files this might be ok.
 *
 * 3) Put all your files in one archive. Then there will be only one file and
 * method 1 will work well.
 *
 * 4) Figure out the total size in bytes of the files you will download and put
 * that number in your application, then use LoadInfo.getTotalBytesDownloaded()
 * / MY_APPS_TOTAL_BYTES_TO_DOWNLOAD.
 *
 * @return {number} The total number of currently known bytes to be streamed.
 */
o3djs.io.LoadInfo.prototype.getTotalKnownBytesToStreamSoFar = function() {
  if (!this.streamLength_ && this.request_ && this.hasStatus_) {
    this.streamLength_ = this.request_.streamLength;
  }
  var total = this.streamLength_;
  for (var cc = 0; cc < this.children_.length; ++cc) {
    total += this.children_[cc].getTotalKnownBytesToStreamSoFar();
  }
  return total;
};

/**
 * Gets the total bytes downloaded so far.
 * @return {number} The total number of currently known bytes to be streamed.
 */
o3djs.io.LoadInfo.prototype.getTotalBytesDownloaded = function() {
  var total = (this.request_ && this.hasStatus_) ?
              this.request_.bytesReceived : this.streamLength_;
  for (var cc = 0; cc < this.children_.length; ++cc) {
    total += this.children_[cc].getTotalBytesDownloaded();
  }
  return total;
};

/**
 * Gets the total streams that will be download known so far.
 * We can't know all the streams since you could use an o3djs.loader.Loader
 * object, request some streams, then call this function, then request some
 * more.
 *
 * See LoadInfo.getTotalKnownBytesToStreamSoFar for details.
 * @return {number} The total number of requests currently known to be streamed.
 * @see o3djs.io.LoadInfo.getTotalKnownBytesToStreamSoFar
 */
o3djs.io.LoadInfo.prototype.getTotalKnownRequestsToStreamSoFar = function() {
  var total = 1;
  for (var cc = 0; cc < this.children_.length; ++cc) {
    total += this.children_[cc].getTotalKnownRequestToStreamSoFar();
  }
  return total;
};

/**
 * Gets the total requests downloaded so far.
 * @return {number} The total requests downloaded so far.
 */
o3djs.io.LoadInfo.prototype.getTotalRequestsDownloaded = function() {
  var total = this.request_ ? 0 : 1;
  for (var cc = 0; cc < this.children_.length; ++cc) {
    total += this.children_[cc].getTotalRequestsDownloaded();
  }
  return total;
};

/**
 * Gets progress info.
 * This is commonly formatted version of the information available from a
 * LoadInfo.
 *
 * See LoadInfo.getTotalKnownBytesToStreamSoFar for details.
 * @return {{percent: number, downloaded: string, totalBytes: string,
 *     base: number, suffix: string}} progress info.
 * @see o3djs.io.LoadInfo.getTotalKnownBytesToStreamSoFar
 */
o3djs.io.LoadInfo.prototype.getKnownProgressInfoSoFar = function() {
  var percent = 0;
  var bytesToDownload = this.getTotalKnownBytesToStreamSoFar();
  var bytesDownloaded = this.getTotalBytesDownloaded();
  if (bytesToDownload > 0) {
    percent = Math.floor(bytesDownloaded / bytesToDownload * 100);
  }

  var base = (bytesToDownload < 1024 * 1024) ? 1024 : (1024 * 1024);

  return {
    percent: percent,
    downloaded: (bytesDownloaded / base).toFixed(2),
    totalBytes: (bytesToDownload / base).toFixed(2),
    base: base,
    suffix: (base == 1024 ? 'kb' : 'mb')}

};

/**
 * Loads text from an external file. This function is synchronous.
 * @param {string} url The url of the external file.
 * @return {string} the loaded text if the request is synchronous.
 */
o3djs.io.loadTextFileSynchronous = function(url) {
  o3djs.BROWSER_ONLY = true;

  var error = 'loadTextFileSynchronous failed to load url "' + url + '"';
  var request;
  if (!o3djs.base.IsMSIE() && window.XMLHttpRequest) {
    request = new XMLHttpRequest();
    if (request.overrideMimeType) {
      request.overrideMimeType('text/plain');
    }
  } else if (window.ActiveXObject) {
    request = new ActiveXObject('MSXML2.XMLHTTP.3.0');
  } else {
    throw 'XMLHttpRequest is disabled';
  }
  request.open('GET', url, false);
  request.send(null);
  if (request.readyState != 4) {
    throw error;
  }
  return request.responseText;
};

/**
 * Loads text from an external file. This function is asynchronous.
 * @param {string} url The url of the external file.
 * @param {function(string, *): void} callback A callback passed the loaded
 *     string and an exception which will be null on success.
 * @param {function(object): void} onprogress A callback passed the onprogress
 *     event.
 * @return {!o3djs.io.LoadInfo} A LoadInfo to track progress.
 */
o3djs.io.loadTextFile = function(url, callback, onprogress) {
  o3djs.BROWSER_ONLY = true;

  var error = 'loadTextFile failed to load url "' + url + '"';
  var request;
  if (!o3djs.base.IsMSIE() && window.XMLHttpRequest) {
    request = new XMLHttpRequest();
    if (request.overrideMimeType) {
      request.overrideMimeType('text/plain');
    }
  } else if (window.ActiveXObject) {
    request = new ActiveXObject('MSXML2.XMLHTTP.3.0');
  } else {
    throw 'XMLHttpRequest is disabled';
  }
  var loadInfo = o3djs.io.createLoadInfo(request, false);
  request.open('GET', url, true);
  var finish = function() {
    if (request.readyState == 4) {
      var text = '';
      // HTTP reports success with a 200 status. The file protocol reports
      // success with zero. HTTP does not use zero as a status code (they
      // start at 100).
      // https://developer.mozilla.org/En/Using_XMLHttpRequest
      var success = request.status == 200 || request.status == 0;
      if (success) {
        text = request.responseText;
      }
      loadInfo.finish();
      callback(text, success ? null : 'could not load: ' + url);
    }
  };
  request.onreadystatechange = finish;
  request.onprogress = onprogress;
  request.send(null);
  return loadInfo;
};

/**
 * A ArchiveInfo object loads and manages an archive of files.
 * There are methods for locating a file by uri and for freeing
 * the archive.
 *
 * You can only read archives that have as their first file a file named
 * 'aaaaaaaa.o3d' the contents of the which is 'o3d'. This is to prevent O3D
 * from being used to read arbitrary tar gz files.
 *
 * Example:
 * <pre>
 * var loadInfo = o3djs.io.loadArchive(pack,
 *                                     'http://google.com/files.o3dtgz',
 *                                     callback);
 *
 * function callback(archiveInfo, exception) {
 *   if (!exception) {
 *     o3djs.texture.createTextureFromRawData(
 *         pack, archiveInfo.getFileByURI('logo.jpg'), true);
 *     o3djs.texture.createTextureFromRawData(
 *         pack, archiveInfo.getFileByURI('wood/oak.png'), true);
 *     o3djs.texture.createTextureFromRawData(
 *         pack, archiveInfo.getFileByURI('wood/maple.dds'), true);
 *     archiveInfo.destroy();
 *   } else {
 *     alert(exception);
 *   }
 * }
 * </pre>
 *
 * @constructor
 * @param {!o3d.Pack} pack Pack to create request in.
 * @param {string} url The url of the archive file.
 * @param {!function(!o3djs.io.ArchiveInfo, *): void} onFinished A
 *     callback that is called when the archive is finished loading and passed
 *     the ArchiveInfo and an exception which is null on success.
 */
o3djs.io.ArchiveInfo = function(pack, url, onFinished) {
  var that = this;

  /**
   * The list of files in the archive by uri.
   * @type {!Object}
   */
  this.files = {};

  /**
   * The pack used to create the archive request.
   * @type {!o3d.Pack}
   */
  this.pack = pack;

  /**
   * True if this archive has not be destroyed.
   * @type {boolean}
   */
  this.destroyed = false;

  this.request_ = null;

  /**
   * Records each RawData file as it comes in.
   * @private
   * @param {!o3d.RawData} rawData RawData from archive request.
   */
  function addFile(rawData) {
    that.files[rawData.uri] = rawData;
  }

  /**
   * The LoadInfo to track loading progress.
   * @type {!o3djs.io.LoadInfo}
   */
  this.loadInfo = o3djs.io.loadArchiveAdvanced(
      pack,
      url,
      addFile,
      function(request, exception) {
        that.request_ = request;
        onFinished(that, exception);
      });
};

/**
 * Releases all the RAW data associated with this archive. It does not release
 * any objects created from that RAW data.
 */
o3djs.io.ArchiveInfo.prototype.destroy = function() {
  if (!this.destroyed) {
    this.pack.removeObject(this.request_);
    this.destroyed = true;
    this.files = {};
  }
};

/**
 * Gets files by regular expression or wildcards from the archive.
 * @param {(string|!RegExp)} uri of file to get. Can have wildcards '*' and '?'.
 * @param {boolean} opt_caseInsensitive Only valid if it's a wildcard string.
 * @return {!Array.<!o3d.RawData>} An array of the matching RawDatas for
 *      the files matching or undefined if it doesn't exist.
 */
o3djs.io.ArchiveInfo.prototype.getFiles = function(uri,
                                                   opt_caseInsensitive) {
  if (!(uri instanceof RegExp)) {
    uri = uri.replace(/(\W)/g, '\\$&');
    uri = uri.replace(/\\\*/g, '.*');
    uri = uri.replace(/\\\?/g, '.');
    uri = new RegExp(uri, opt_caseInsensitive ? 'i' : '');
  }
  var files = [];
  for (var key in this.files) {
    if (uri.test(key)) {
      files.push(this.files[key]);
    }
  }

  return files;
};

/**
 * Gets a file by URI from the archive.
 * @param {string} uri of file to get.
 * @param {boolean} opt_caseInsensitive True to be case insensitive. Default
 *      false.
 * @return {(o3d.RawData|undefined)} The RawData for the file or undefined if
 *      it doesn't exist.
 */
o3djs.io.ArchiveInfo.prototype.getFileByURI = function(
    uri,
    opt_caseInsensitive) {
  if (opt_caseInsensitive) {
    uri = uri.toLowerCase();
    for (var key in this.files) {
      if (key.toLowerCase() == uri) {
        return this.files[key];
      }
    }
    return undefined;
  } else {
    return this.files[uri];
  }
};

/**
 * Loads an archive file.
 * When the entire archive is ready the onFinished callback will be called
 * with an ArchiveInfo for accessing the archive.
 *
 * @param {!o3d.Pack} pack Pack to create request in.
 * @param {string} url The url of the archive file.
 * @param {!function(!o3djs.io.ArchiveInfo, *): void} onFinished A
 *     callback that is called when the archive is successfully loaded and an
 *     Exception object which is null on success.
 * @return {!o3djs.io.LoadInfo} The a LoadInfo for tracking progress.
 * @see o3djs.io.ArchiveInfo
 */
o3djs.io.loadArchive = function(pack,
                                url,
                                onFinished) {
  var archiveInfo = new o3djs.io.ArchiveInfo(pack, url, onFinished);
  return archiveInfo.loadInfo;
};

/**
 * Loads an archive file. This function is asynchronous. This is a low level
 * version of o3djs.io.loadArchive which can be used for things like
 * progressive loading.
 *
 * @param {!o3d.Pack} pack Pack to create request in.
 * @param {string} url The url of the archive file.
 * @param {!function(!o3d.RawData): void} onFileAvailable A callback, taking a
 *     single argument 'data'. As each file is loaded from the archive, this
 *     function is called with the file's data.
 * @param {!function(!o3d.ArchiveRequest, *): void} onFinished
 *     A callback that is called when the archive is successfully loaded. It is
 *     passed the ArchiveRquest and null on success or a javascript exception on
 *     failure.
 * @return {!o3djs.io.LoadInfo} A LoadInfo for tracking progress.
 */
o3djs.io.loadArchiveAdvanced = function(pack,
                                        url,
                                        onFileAvailable,
                                        onFinished) {
  var error = 'loadArchive failed to load url "' + url + '"';
  var request = pack.createArchiveRequest();
  var loadInfo = o3djs.io.createLoadInfo(request, true);
  request.open('GET', url);
  request.onfileavailable = onFileAvailable;
  /**
   * @ignore
   */
  request.onreadystatechange = function() {
    if (request.done) {
      loadInfo.finish();
      var success = request.success;
      var exception = null;
      if (!success) {
        exception = request.error;
        if (!exception) {
          exception = 'unknown error loading archive';
        }
      }
      onFinished(request, exception);
    }
  };
  request.send();
  return loadInfo;
};

/**
 * Loads RawData.
 *
 * RawData is loaded asynchronously.
 *
 * @param {!o3d.Pack} pack Pack to create the request in.
 * @param {string} url URL of raw data to load.
 * @param {!function(!o3d.FileRequest, o3d.RawData, *): void} callback Callback
 *     when RawData is loaded. It will be passed the FileRequest, a RawData and
 *     an exception on error or null on success. The RawData is associated with
 *     the request so it will stay in memory until you free with request with
 *     pack.removeObject(request).
 * @return {!o3djs.io.LoadInfo} A LoadInfo to track progress.
 * @see o3djs.io.loadTexture
 * @see o3djs.io.loadBitmaps
 * @see o3djs.loader.createLoader
 */
o3djs.io.loadRawData = function(pack, url, callback) {
  var request = pack.createFileRequest('RAWDATA');
  var loadInfo = o3djs.io.createLoadInfo(
      /** @type {!o3d.FileRequest} */ (request),
      false);
  request.open('GET', url, true);
  /**
   * @ignore
   */
  request.onreadystatechange = function() {
    if (request.done) {
      var data = request.data;
      var success = request.success;
      var exception = request.error;
      loadInfo.finish();
      if (!success && !exception) {
          exception = 'unknown error loading RawData: ' + url;
      }
      callback(request, data, success ? null : exception);
    }
  };
  request.send();
  return loadInfo;
};

/**
 * Loads bitmaps.
 *
 * Bitmaps are loaded asynchronously.
 *
 * Example:
 * <pre>
 * var loadInfo = o3djs.io.loadBitamps(pack,
 *                                     'http://google.com/someimage.jpg',
 *                                     callback);
 *
 * function callback(bitmaps, exception) {
 *   if (!exception) {
 *     o3djs.texture.createTextureFromBitmaps(g_pack, bitmaps, true);
 *   } else {
 *     alert(exception);
 *   }
 * }
 * </pre>
 *
 *
 * @param {!o3d.Pack} pack Pack to load texture into.
 * @param {string} url URL of image to load.
 * @param {!function(!Array.<!o3d.Bitmap>, *): void} callback Callback when
 *     image is loaded. It will be passed an array of bitmaps and an exception
 *     on error or null on success.
 * @param {boolean} opt_generateMips Generate Mips. Default = true.
 * @return {!o3djs.io.LoadInfo} A LoadInfo to track progress.
 * @see o3djs.io.loadTexture
 * @see o3djs.loader.createLoader
 */
o3djs.io.loadBitmaps = function(pack, url, callback, opt_generateMips) {
  if (typeof opt_generateMips === 'undefined') {
    opt_generateMips = true;
  }
  return o3djs.io.loadRawData(pack, url, function(request, rawData, exception) {
    var bitmaps = [];
    if (!exception) {
      bitmaps = pack.createBitmapsFromRawData(rawData);
      pack.removeObject(request);
    }
    callback(bitmaps, exception);
  });
};

/**
 * Loads a texture.
 *
 * Textures are loaded asynchronously.
 *
 * Example:
 * <pre>
 * var loadInfo = o3djs.io.loadTexture(pack,
 *                                     'http://google.com/someimage.jpg',
 *                                     callback);
 *
 * function callback(texture, exception) {
 *   if (!exception) {
 *     g_mySampler.texture = texture;
 *   } else {
 *     alert(exception);
 *   }
 * }
 * </pre>
 *
 *
 * @param {!o3d.Pack} pack Pack to load texture into.
 * @param {string} url URL of texture to load.
 * @param {!function(o3d.Texture, *): void} callback Callback when
 *     texture is loaded. It will be passed the texture and an exception on
 *     error or null on success.
 * @param {boolean} opt_generateMips Generate Mips. Default = true.
 * @param {boolean} opt_flip Flip texture. Default = true.
 * @return {!o3djs.io.LoadInfo} A LoadInfo to track progress.
 * @see o3djs.io.loadBitmaps
 * @see o3djs.loader.createLoader
 */
o3djs.io.loadTexture = function(
    pack, url, callback, opt_generateMips, opt_flip) {
  function onLoaded(request, rawData, exception) {
    var texture = null;
    if (!exception) {
      texture = o3djs.texture.createTextureFromRawData(
          pack, rawData, opt_generateMips, opt_flip);
      pack.removeObject(request);
    }
    callback(texture, exception);
  };

  return o3djs.io.loadRawData(pack, url, onLoaded);
};


/*
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview This file contains various functions and classes for dealing
 * with 3d scenes.
 */

o3djs.provide('o3djs.scene');

/**
 * A Module with various scene functions and classes.
 * @namespace
 */
o3djs.scene = o3djs.scene || {};

/**
 * Loads a scene.
 * @param {!o3d.Client} client An O3D client object.
 * @param {!o3d.Pack} pack Pack to load scene into.
 * @param {!o3d.Transform} parent Transform to parent scene under.
 * @param {string} url URL of scene to load.
 * @param {!function(!o3d.Pack, !o3d.Transform, *): void} callback
 *     Callback when scene is loaded. It will be passed the pack, the parent and
 *     an exception which is null on success.
 * @param {!o3djs.serialization.Options} opt_options Options passed into the
 *     loader.
 * @return {!o3djs.io.LoadInfo} A LoadInfo for tracking progress.
 * @see o3djs.loader.createLoader
 */
o3djs.scene.loadScene = function(client,
                                 pack,
                                 parent,
                                 url,
                                 callback,
                                 opt_options) {
  // Starts the deserializer once the entire archive is available.
  function onFinished(archiveInfo, exception) {
    if (!exception) {
      var finishCallback = function(pack, parent, exception) {
        archiveInfo.destroy();
        callback(pack, parent, exception);
      };
      o3djs.serialization.deserializeArchive(archiveInfo,
                                             'scene.json',
                                             client,
                                             pack,
                                             parent,
                                             finishCallback,
                                             opt_options);
    } else {
      archiveInfo.destroy();
      callback(pack, parent, exception);
    }
  }
  return o3djs.io.loadArchive(pack, url, onFinished);
};


/*
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview This file provides support for deserializing (loading)
 *     transform graphs from JSON files.
 *
 */

o3djs.provide('o3djs.serialization');

/**
 * A Module for deserializing a scene created by the sample o3dConverter.
 * @namespace
 */
o3djs.serialization = o3djs.serialization || {};

/**
 * The oldest supported version of the serializer. It isn't necessary to
 * increment this version whenever the format changes. Only change it when the
 * deserializer becomes incapable of deserializing an older version.
 * @type {number}
 */
o3djs.serialization.supportedVersion = 5;

/**
 * These are the values the sample o3dConverter uses to identify curve key
 * types.
 * @type {!Object}
 */
o3djs.serialization.CURVE_KEY_TYPES = {
  step: 1,
  linear: 2,
  bezier: 3};

/**
 * Options for deserialization.
 *
 * opt_animSource is an optional ParamFloat that will be bound as the source
 * param for all animation time params in the scene. opt_async is a bool that
 * will make the deserialization process async.
 *
 * @type {{opt_animSource: !o3d.ParamFloat, opt_async: boolean}}
 */
o3djs.serialization.Options = goog.typedef;

/**
 * A Deserializer incrementally deserializes a transform graph.
 * @constructor
 * @param {!o3d.Pack} pack The pack to deserialize into.
 * @param {!Object} json An object tree conforming to the JSON rules.
 */
o3djs.serialization.Deserializer = function(pack, json) {
  /**
   * The pack to deserialize into.
   * @type {!o3d.Pack}
   */
  this.pack = pack;

  /**
   * An object tree conforming to the JSON rules.
   * @type {!Object}
   */
  this.json = json;

  /**
   * The archive from which assets referenced from JSON are retreived.
   * @type {o3djs.io.ArchiveInfo}
   */
  this.archiveInfo = null;

  /**
   * Deserializes a Buffer .
   * @param {!o3djs.serialization.Deserializer} deserializer The deserializer.
   * @param {!Object} json The json for this buffer.
   * @param {string} type The type of buffer to create.
   * @param {string} uri The uri of the file containing the binary data.
   */
  function deserializeBuffer(deserializer, json, type, uri) {
    var object = deserializer.pack.createObject(type);
    if ('custom' in json) {
      if ('fieldData' in json.custom) {
        var fieldDataArray = json.custom.fieldData;
        if (fieldDataArray.length > 0) {
          var fields = [];
          // First create all the fields
          for (var ii = 0; ii < fieldDataArray.length; ++ii) {
            var data = fieldDataArray[ii];
            var field = object.createField(data.type, data.numComponents);
            fields.push(field);
            deserializer.addObject(data.id, field);
          }
          var firstData = fieldDataArray[0];
          var numElements = firstData.data.length / firstData.numComponents;
          object.allocateElements(numElements);
          // Now set the data.
          for (var ii = 0; ii < fieldDataArray.length; ++ii) {
            var data = fieldDataArray[ii];
            fields[ii].setAt(0, data.data);
          }
        }
      } else {
        var rawData = deserializer.archiveInfo.getFileByURI(uri);
        object.set(rawData,
                   json.custom.binaryRange[0],
                   json.custom.binaryRange[1] - json.custom.binaryRange[0]);
        for (var i = 0; i < json.custom.fields.length; ++i) {
          deserializer.addObject(json.custom.fields[i], object.fields[i]);
        }
      }
    }
    return object;
  }

  /**
   * A map from classname to a function that will create
   * instances of objects. Add entries to support additional classes.
   * @type {!Object}
   */
  this.createCallbacks = {
    'o3djs.DestinationBuffer': function(deserializer, json) {
      var object = deserializer.pack.createObject('o3d.VertexBuffer');
      if ('custom' in json) {
        for (var i = 0; i < json.custom.fields.length; ++i) {
          var fieldInfo = json.custom.fields[i]
          var field = object.createField(fieldInfo.type,
                                         fieldInfo.numComponents);
          deserializer.addObject(fieldInfo.id, field);
        }
        object.allocateElements(json.custom.numElements);
      }
      return object;
    },

    'o3d.VertexBuffer': function(deserializer, json) {
      return deserializeBuffer(
          deserializer, json, 'o3d.VertexBuffer', 'vertex-buffers.bin');
    },

    'o3d.SourceBuffer': function(deserializer, json) {
      return deserializeBuffer(
          deserializer, json, 'o3d.SourceBuffer', 'vertex-buffers.bin');
    },

    'o3d.IndexBuffer': function(deserializer, json) {
      return deserializeBuffer(
          deserializer, json, 'o3d.IndexBuffer', 'index-buffers.bin');
    },

    'o3d.Texture2D': function(deserializer, json) {
      if ('o3d.uri' in json.params) {
        var uri = json.params['o3d.uri'].value;
        var rawData = deserializer.archiveInfo.getFileByURI(uri);
        if (!rawData) {
          throw 'Could not find texture ' + uri + ' in the archive';
        }
        return o3djs.texture.createTextureFromRawData(pack, rawData, true);
      } else {
        return deserializer.pack.createTexture2D(
            json.custom.width,
            json.custom.height,
            json.custom.format,
            json.custom.levels,
            json.custom.renderSurfacesEnabled);
      }
    },

    'o3d.TextureCUBE': function(deserializer, json) {
      if ('o3d.negx_uri' in json.params) {
        // Cube map comprised of six separate textures.
        var param_names = [
            'o3d.posx_uri',
            'o3d.negx_uri',
            'o3d.posy_uri',
            'o3d.negy_uri',
            'o3d.posz_uri',
            'o3d.negz_uri'
        ];
        var rawDataArray = [];
        for (var i = 0; i < param_names.length; i++) {
          var uri = json.params[param_names[i]].value;
          var rawData = deserializer.archiveInfo.getFileByURI(uri);
          if (!rawData) {
            throw 'Could not find texture ' + uri + ' in the archive';
          }
          rawDataArray.push(rawData);
        }
        // Cube map faces should not be flipped.
        return o3djs.texture.createTextureFromRawDataArray(
            pack, rawDataArray, true, false);
      } else if ('o3d.uri' in json.params) {
        var uri = json.params['o3d.uri'].value;
        var rawData = deserializer.archiveInfo.getFileByURI(uri);
        if (!rawData) {
          throw 'Could not find texture ' + uri + ' in the archive';
        }
        return o3djs.texture.createTextureFromRawData(pack, rawData, true);
      } else {
        return deserializer.pack.createTextureCUBE(
            json.custom.edgeLength,
            json.custom.format,
            json.custom.levels,
            json.custom.renderSurfacesEnabled);
      }
    }
  };

  /**
   * A map from classname to a function that will initialize
   * instances of the given class from JSON data. Add entries to support
   * additional classes.
   * @type {!Object}
   */
  this.initCallbacks = {
    'o3d.Curve': function(deserializer, object, json) {
      if ('custom' in json) {
        if ('keys' in json.custom) {
          var keys = json.custom.keys;
          var stepType = o3djs.serialization.CURVE_KEY_TYPES.step;
          var linearType = o3djs.serialization.CURVE_KEY_TYPES.linear;
          var bezierType = o3djs.serialization.CURVE_KEY_TYPES.bezier;
          for (var ii = 0; ii < keys.length; ++ii) {
            var key = keys[ii];
            switch (key[0]) {
            case stepType:  // Step
              object.addStepKeys(key.slice(1));
              break;
            case linearType:  // Linear
              object.addLinearKeys(key.slice(1));
              break;
            case bezierType:  // Bezier
              object.addBezierKeys(key.slice(1));
              break;
            }
          }
        } else {
          var rawData = deserializer.archiveInfo.getFileByURI('curve-keys.bin');
          object.set(rawData,
                     json.custom.binaryRange[0],
                     json.custom.binaryRange[1] - json.custom.binaryRange[0]);
        }
      }
    },

    'o3d.Effect': function(deserializer, object, json) {
      var uriParam = object.getParam('o3d.uri');
      if (uriParam) {
        var rawData = deserializer.archiveInfo.getFileByURI(uriParam.value);
        if (!rawData) {
          throw 'Cannot find shader ' + uriParam.value + ' in archive.';
        }
        if (!object.loadFromFXString(rawData.stringValue)) {
          throw 'Cannot load shader ' + uriParam.value + ' in archive.';
        }
      }
    },

    'o3d.Skin': function(deserializer, object, json) {
      if ('custom' in json) {
        if ('binaryRange' in json.custom) {
          var rawData = deserializer.archiveInfo.getFileByURI('skins.bin');
          object.set(rawData,
                     json.custom.binaryRange[0],
                     json.custom.binaryRange[1] - json.custom.binaryRange[0]);
        }
      }
    },

    'o3d.SkinEval': function(deserializer, object, json) {
      if ('custom' in json) {
        for (var i = 0; i < json.custom.vertexStreams.length; ++i) {
          var streamJson = json.custom.vertexStreams[i];
          var field = deserializer.getObjectById(streamJson.stream.field);
          object.setVertexStream(streamJson.stream.semantic,
                                 streamJson.stream.semanticIndex,
                                 field,
                                 streamJson.stream.startIndex);
          if ('bind' in streamJson) {
            var source = deserializer.getObjectById(streamJson.bind);
            object.bindStream(source,
                              streamJson.stream.semantic,
                              streamJson.stream.semanticIndex);
          }
        }
      }
    },

    'o3d.StreamBank': function(deserializer, object, json) {
      if ('custom' in json) {
        for (var i = 0; i < json.custom.vertexStreams.length; ++i) {
          var streamJson = json.custom.vertexStreams[i];
          var field = deserializer.getObjectById(streamJson.stream.field);
          object.setVertexStream(streamJson.stream.semantic,
                                 streamJson.stream.semanticIndex,
                                 field,
                                 streamJson.stream.startIndex);
          if ('bind' in streamJson) {
            var source = deserializer.getObjectById(streamJson.bind);
            object.bindStream(source,
                              streamJson.stream.semantic,
                              streamJson.stream.semanticIndex);
          }
        }
      }
    }
  };

  if (!('version' in json)) {
    throw 'Version in JSON file was missing.';
  }

  if (json.version < o3djs.serialization.supportedVersion) {
    throw 'Version in JSON file was ' + json.version +
        ' but expected at least version ' +
        o3djs.serialization.supportedVersion + '.';
  }

  if (!('objects' in json)) {
    throw 'Objects array in JSON file was missing.';
  }

  /**
   * An array of all objects deserialized so far, indexed by object id. Id zero
   * means null.
   * @type {!Array.<(Object|undefined)>}
   * @private
   */
  this.objectsById_ = [null];

  /**
   * An array of objects deserialized so far, indexed by position in the JSON.
   * @type {!Array.<Object>}
   * @private
   */
  this.objectsByIndex_ = [];

  /**
   * Array of all classes present in the JSON.
   * @type {!Array.<string>}
   * @private
   */
  this.classNames_ = [];
  for (var className in json.objects) {
    this.classNames_.push(className);
  }

  /**
   * The current phase_ of deserialization. In phase_ 0, objects
   * are created and their ids registered. In phase_ 1, objects are
   * initialized from JSON data.
   * @type {number}
   * @private
   */
  this.phase_ = 0;

  /**
   * Index of the next class to be deserialized in classNames_.
   * @type {number}
   * @private
   */
  this.nextClassIndex_ = 0;

  /**
   * Index of the next object of the current class to be deserialized.
   * @type {number}
   * @private
   */
  this.nextObjectIndex_ = 0;

  /**
   * Index of the next object to be deserialized in objectsByIndex_.
   * @type {number}
   * @private
   */
  this.globalObjectIndex_ = 0;
};

/**
 * Get the object with the given id.
 * @param {number} id The id to lookup.
 * @return {(Object|undefined)} The object with the given id.
 */
o3djs.serialization.Deserializer.prototype.getObjectById = function(id) {
  return this.objectsById_[id];
};

/**
 * When a creation or init callback creates an object that the Deserializer
 * is not aware of, it can associate it with an id using this function, so that
 * references to the object can be resolved.
 * @param {number} id The is of the object.
 * @param {!Object} object The object to register.
 */
o3djs.serialization.Deserializer.prototype.addObject = function(
    id, object) {
  this.objectsById_[id] = object;
};

/**
 * Deserialize a value. Identifies reference values and converts
 * their object id into an object reference. Otherwise returns the
 * value unchanged.
 * @param {*} valueJson The JSON representation of the value.
 * @return {*} The JavaScript representation of the value.
 */
o3djs.serialization.Deserializer.prototype.deserializeValue = function(
    valueJson) {
  if (typeof(valueJson) === 'object') {
    if (valueJson === null) {
      return null;
    }

    var valueAsObject = /** @type {!Object} */ (valueJson);
    if ('length' in valueAsObject) {
      for (var i = 0; i != valueAsObject.length; ++i) {
        valueAsObject[i] = this.deserializeValue(valueAsObject[i]);
      }
      return valueAsObject;
    }

    var refId = valueAsObject['ref'];
    if (refId !== undefined) {
      var referenced = this.objectsById_[refId];
      if (referenced === undefined) {
        throw 'Could not find object with id ' + refId + '.';
      }
      return referenced;
    }
  }

  return valueJson;
};

/**
 * Sets the value of a param on an object or binds a param to another.
 * @param {!Object} object The object holding the param.
 * @param {(string|number)} paramName The name of the param.
 * @param {!Object} propertyJson The JSON representation of the value.
 * @private
 */
o3djs.serialization.Deserializer.prototype.setParamValue_ = function(
    object, paramName, propertyJson) {
  var param = object.getParam(paramName);
  if (param === null)
    return;

  var valueJson = propertyJson['value'];
  if (valueJson !== undefined) {
    param.value = this.deserializeValue(valueJson);
  }

  var bindId = propertyJson['bind'];
  if (bindId !== undefined) {
    var referenced = this.objectsById_[bindId];
    if (referenced === undefined) {
      throw 'Could not find output param with id ' + bindId + '.';
    }
    param.bind(referenced);
  }
};

/**
 * Creates a param on an object and adds it's id so that other objects can
 * reference it.
 * @param {!Object} object The object to hold the param.
 * @param {(string|number)} paramName The name of the param.
 * @param {!Object} propertyJson The JSON representation of the value.
 * @private
 */
o3djs.serialization.Deserializer.prototype.createAndIdentifyParam_ =
    function(object, paramName, propertyJson) {
  var propertyClass = propertyJson['class'];
  var param;
  if (propertyClass !== undefined) {
    param = object.createParam(paramName, propertyClass);
  } else {
    param = object.getParam(paramName);
  }

  var paramId = propertyJson['id'];
  if (paramId !== undefined && param !== null) {
    this.objectsById_[paramId] = param;
  }
};

/**
 * First pass: create all objects and additional params. We need two
 * passes to support references to objects that appear later in the
 * JSON.
 * @param {number} amountOfWork The number of loop iterations to perform of
 *     this phase_.
 * @private
 */
o3djs.serialization.Deserializer.prototype.createObjectsPhase_ =
     function(amountOfWork) {
  for (; this.nextClassIndex_ < this.classNames_.length;
       ++this.nextClassIndex_) {
    var className = this.classNames_[this.nextClassIndex_];
    var classJson = this.json.objects[className];
    var numObjects = classJson.length;
    for (; this.nextObjectIndex_ < numObjects; ++this.nextObjectIndex_) {
      if (amountOfWork-- <= 0)
        return;

      var objectJson = classJson[this.nextObjectIndex_];
      var object = undefined;
      if ('id' in objectJson) {
        object = this.objectsById_[objectJson.id];
      }
      if (object === undefined) {
        if (className in this.createCallbacks) {
          object = this.createCallbacks[className](this, objectJson);
        } else {
          object = this.pack.createObject(className);
        }
      }
      this.objectsByIndex_[this.globalObjectIndex_++] = object;
      if ('id' in objectJson) {
        this.objectsById_[objectJson.id] = object;
      }
      if ('params' in objectJson) {
        if ('length' in objectJson.params) {
          for (var paramIndex = 0; paramIndex != objectJson.params.length;
              ++paramIndex) {
            var paramJson = objectJson.params[paramIndex];
            this.createAndIdentifyParam_(object, paramIndex,
                                         paramJson);
          }
        } else {
          for (var paramName in objectJson.params) {
            var paramJson = objectJson.params[paramName];
            this.createAndIdentifyParam_(object, paramName, paramJson);
          }
        }
      }
    }
    this.nextObjectIndex_ = 0;
  }

  if (this.nextClassIndex_ === this.classNames_.length) {
    this.nextClassIndex_ = 0;
    this.nextObjectIndex_ = 0;
    this.globalObjectIndex_ = 0;
    ++this.phase_;
  }
};

/**
 * Second pass: set property and parameter values and bind parameters.
 * @param {number} amountOfWork The number of loop iterations to perform of
 *     this phase_.
 * @private
 */
o3djs.serialization.Deserializer.prototype.setPropertiesPhase_ = function(
    amountOfWork) {
  for (; this.nextClassIndex_ < this.classNames_.length;
       ++this.nextClassIndex_) {
    var className = this.classNames_[this.nextClassIndex_];
    var classJson = this.json.objects[className];
    var numObjects = classJson.length;
    for (; this.nextObjectIndex_ < numObjects; ++this.nextObjectIndex_) {
      if (amountOfWork-- <= 0)
        return;

      var objectJson = classJson[this.nextObjectIndex_];
      var object = this.objectsByIndex_[this.globalObjectIndex_++];
      if ('properties' in objectJson) {
        for (var propertyName in objectJson.properties) {
          if (propertyName in object) {
            var propertyJson = objectJson.properties[propertyName];
            var propertyValue = this.deserializeValue(propertyJson);
            object[propertyName] = propertyValue;
          }
        };
      }
      if ('params' in objectJson) {
        if ('length' in objectJson.params) {
          for (var paramIndex = 0; paramIndex != objectJson.params.length;
              ++paramIndex) {
            var paramJson = objectJson.params[paramIndex];
            this.setParamValue_(/** @type {!Object} */ (object),
                                paramIndex,
                                paramJson);
          }
        } else {
          for (var paramName in objectJson.params) {
            var paramJson = objectJson.params[paramName];
            this.setParamValue_(/** @type {!Object} */ (object),
                                paramName,
                                paramJson);
          }
        }
      }
      if (className in this.initCallbacks) {
        this.initCallbacks[className](this, object, objectJson);
      }
    }
    this.nextObjectIndex_ = 0;
  }

  if (this.nextClassIndex_ === this.classNames_.length) {
    this.nextClassIndex_ = 0;
    this.nextObjectIndex_ = 0;
    this.globalObjectIndex_ = 0;
    ++this.phase_;
  }
};

/**
 * Perform a certain number of iterations of the deserializer. Keep calling this
 * function until it returns false.
 * @param {number} opt_amountOfWork The number of loop iterations to run. If
 *     not specified, runs the deserialization to completion.
 * @return {boolean} Whether work remains to be done.
 */
o3djs.serialization.Deserializer.prototype.run = function(
    opt_amountOfWork) {
  if (!opt_amountOfWork) {
    while (this.run(10000)) {
    }
    return false;
  } else {
    switch (this.phase_) {
    case 0:
      this.createObjectsPhase_(opt_amountOfWork);
      break;
    case 1:
      this.setPropertiesPhase_(opt_amountOfWork);
      break;
    }
    return this.phase_ < 2;
  }
};

/**
 * Deserializes (loads) a transform graph in the background. Invokes
 * a callback function on completion passing the pack and the thrown
 * exception on failure or the pack and a null exception on success.
 * @param {!o3d.Client} client An O3D client object.
 * @param {!o3d.Pack} pack The pack to create the deserialized objects
 *     in.
 * @param {number} time The amount of the time (in seconds) the deserializer
 *     should aim to complete in.
 * @param {!function(o3d.Pack, *): void} callback The function that
 *     is called on completion. The second parameter is null on success or
 *     the thrown exception on failure.
 */
o3djs.serialization.Deserializer.prototype.runBackground = function(
    client, pack, time, callback) {
  // TODO: This seems like it needs to be more granular than the
  //    top level.
  // TODO: Passing in the time you want it to take seems counter
  //   intuitive. I want pass in a % of CPU so I can effectively say
  //   "deserialize this in such a way so as not to affect my app's
  //   performance".  callbacksRequired = numObjects / amountPerCallback where
  //   amountPerCallback = number I can do per frame and not affect performance
  //   too much.
  var workToDo = this.json.objects.length * 2;
  var timerCallbacks = time * 60;
  var amountPerCallback = workToDo / timerCallbacks;
  var intervalId;
  var that = this;
  function deserializeMore() {
    var exception = null;
    var finished = false;
    var failed = false;
    var errorCollector = o3djs.error.createErrorCollector(client);
    try {
      finished = !that.run(amountPerCallback);
    } catch(e) {
      failed = true;
      finished = true;
      exception = e;
    }
    if (errorCollector.errors.length > 0) {
      finished = true;
      exception = errorCollector.errors.join('\n') +
                  (exception ? ('\n' + exception.toString()) : '');
    }
    errorCollector.finish();
    if (finished) {
      window.clearInterval(intervalId);
      callback(pack, exception);
    }
  }

  intervalId = window.setInterval(deserializeMore, 1000 / 60);
};

/**
 * Creates a deserializer that will incrementally deserialize a
 * transform graph. The deserializer object has a method
 * called run that does a fixed amount of work and returns.
 * It returns true until the transform graph is fully deserialized.
 * It returns false from then on.
 * @param {!o3d.Pack} pack The pack to create the deserialized
 *     objects in.
 * @param {!Object} json An object tree conforming to the JSON rules.
 * @return {!o3djs.serialization.Deserializer} A deserializer object.
 */
o3djs.serialization.createDeserializer = function(pack, json) {
  return new o3djs.serialization.Deserializer(pack, json);
};

/**
 * Deserializes a transform graph.
 * @param {!o3d.Pack} pack The pack to create the deserialized
 *     objects in.
 * @param {!Object} json An object tree conforming to the JSON rules.
 */
o3djs.serialization.deserialize = function(pack, json) {
  var deserializer = o3djs.serialization.createDeserializer(pack, json);
  deserializer.run();
};

/**
 * Deserializes a single json object named 'scene.json' from a loaded
 * o3djs.io.ArchiveInfo.
 * @param {!o3djs.io.ArchiveInfo} archiveInfo Archive to load from.
 * @param {string} sceneJsonUri The relative URI of the scene JSON file within
 *     the archive.
 * @param {!o3d.Client} client An O3D client object.
 * @param {!o3d.Pack} pack The pack to create the deserialized objects
 *     in.
 * @param {!o3d.Transform} parent Transform to parent loaded stuff from.
 * @param {!function(!o3d.Pack, !o3d.Transform, *): void} callback A function
 *     that will be called when deserialization is finished. It will be passed
 *     the pack, the parent transform, and an exception which will be null on
 *     success.
 * @param {!o3djs.serialization.Options} opt_options Options.
 */
o3djs.serialization.deserializeArchive = function(archiveInfo,
                                                  sceneJsonUri,
                                                  client,
                                                  pack,
                                                  parent,
                                                  callback,
                                                  opt_options) {
  opt_options = opt_options || { };
  var jsonFile = archiveInfo.getFileByURI(sceneJsonUri);
  if (!jsonFile) {
    throw 'Could not find ' + sceneJsonUri + ' in archive';
  }
  var parsed = eval('(' + jsonFile.stringValue + ')');
  var deserializer = o3djs.serialization.createDeserializer(pack, parsed);

  deserializer.addObject(parsed.o3d_rootObject_root, parent);
  deserializer.archiveInfo = archiveInfo;

  var finishCallback = function(pack, exception) {
    if (!exception) {
      var objects = pack.getObjects('o3d.animSourceOwner', 'o3d.ParamObject');
      if (objects.length > 0) {
        // Rebind the output connections of the animSource to the user's param.
        if (opt_options.opt_animSource) {
          var animSource = objects[0].getParam('animSource');
          var outputConnections = animSource.outputConnections;
          for (var ii = 0; ii < outputConnections.length; ++ii) {
            outputConnections[ii].bind(opt_options.opt_animSource);
          }
        }
        // Remove special object from pack.
        for (var ii = 0; ii < objects.length; ++ii) {
          pack.removeObject(objects[ii]);
        }
      }
    }
    callback(pack, parent, exception);
  };

  if (opt_options.opt_async) {
    // TODO: Remove the 5. See deserializer.runBackground comments.
    deserializer.runBackground(client, pack, 5, finishCallback);
  } else {
    var exception = null;
    var errorCollector = o3djs.error.createErrorCollector(client);
    try {
      deserializer.run();
    } catch (e) {
      exception = e;
    }
    if (errorCollector.errors.length > 0) {
      exception = errorCollector.errors.join('\n') +
                  (exception ? ('\n' + exception.toString()) : '');
    }
    errorCollector.finish();
    finishCallback(pack, exception);
  }
};


/*
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview This file contains various error handing functions for o3d.
 *
 */

o3djs.provide('o3djs.error');

/**
 * A Module with various error handing functions.
 *
 * This module is for helping to manage the client's error callback.
 * Because you can not read the current callback on the client we wrap it with
 * these utilities which track the last callback added so as long as you use
 * o3djs.error.setErrorHandler(client, callback) instead of
 * client.setErrorCallback you'll be able to get and restore the error callback
 * when you need to.
 * @namespace
 */
o3djs.error = o3djs.error || {};

/**
 * A map of error callbacks by client.
 * @private
 * @type {!Array.<(function(string): void|null)>}
 */
o3djs.error.callbacks_ = [];

/**
 * Sets the error handler on a client to a handler that manages the client's
 * error callback.
 * displays an alert on the first error.
 * @param {!o3d.Client} client The client object of the plugin.
 * @param {(function(string): void|null)} callback The callack to use, null to
 *     clear.
 * @return {(function(string): void|null)} the previous error callback for this
 *     client.
 */
o3djs.error.setErrorHandler = function(client, callback) {
  var clientId = client.clientId;
  var old_callback = o3djs.error.callbacks_[clientId];
  o3djs.error.callbacks_[clientId] = callback;
  if (callback) {
    client.setErrorCallback(callback);
  } else {
    client.clearErrorCallback();
  }
  return old_callback;
};

/**
 * Sets a default error handler on the client.
 * The default error handler displays an alert on the first error encountered.
 * @param {!o3d.Client} client The client object of the plugin.
 */
o3djs.error.setDefaultErrorHandler = function(client) {
  o3djs.error.setErrorHandler(
      client,
      function(msg) {
        // Clear the error callback. Otherwise if the callback is happening
        // during rendering it's possible the user will not be able to
        // get out of an infinite loop of alerts.
        o3djs.error.setErrorHandler(client, null);
        alert('ERROR: ' + msg);
      });
};

/**
 * Creates an ErrorCollector.
 * @param {!o3d.Client} client The client object of the plugin.
 * @return {!o3djs.error.ErrorCollector} The created error collector.
 */
o3djs.error.createErrorCollector = function(client) {
  return new o3djs.error.ErrorCollector(client);
};

/**
 * An ErrorCollector takes over the client error callback and continues
 * to collect errors until ErrorCollector.finish() is called.
 * @constructor
 * @param {!o3d.Client} client The client object of the plugin.
 */
o3djs.error.ErrorCollector = function(client) {
  var that = this;
  this.client_ = client;
  /**
   * The collected errors.
   * @type {!Array.<string>}
   */
  this.errors = [];
  this.oldCallback_ = o3djs.error.setErrorHandler(client, function(msg) {
          that.errors.push(msg);
      });
};

/**
 * Stops the ErrorCollector from collecting errors and restores the previous
 * error callback.
 */
o3djs.error.ErrorCollector.prototype.finish = function() {
  o3djs.error.setErrorHandler(this.client_, this.oldCallback_);
};
/*
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview This file contains functions helping to manipulate and manage
 *     textures.
 */

o3djs.provide('o3djs.texture');

/**
 * A Module for bitmaps.
 * @namespace
 */
o3djs.texture = o3djs.texture || {};

/**
 * The maximum dimension of a texture.
 * @type {number}
 */
o3djs.texture.MAX_TEXTURE_DIMENSION = 2048;

/**
 * Computes the maximum number of levels of mips a given width and height could
 * use.
 * @param {number} width Width of texture.
 * @param {number} height Height of texture.
 * @return {number} The maximum number of levels for the given width and height.
 */
o3djs.texture.computeNumLevels = function(width, height) {
  if (width == 0 || height == 0) {
    return 0;
  }
  var max = Math.max(width, height);
  var levels = 0;
  while (max > 0) {
    ++levels;
    max = max >> 1;
  }
  return levels;
};

/**
 * Creates a texture from a RawData object.
 * @param {!o3d.Pack} pack The pack to create the texture in.
 * @param {!o3d.RawData} rawData The raw data to create the texture from.
 * @param {boolean} opt_generateMips Whether or not to generate mips. Note, mips
 *    can not be generated for DXT textures although they will be loaded if they
 *    exist in the RawData.
 * @param {boolean} opt_flip Whether or not to flip the texture. Most DCC tools
 *    Like Maya, Max, etc expect the textures to be flipped.  Note that only
 *    2D (image) textures will be flipped. Cube textures will not be flipped.
 *    Default = true.
 * @param {number} opt_maxWidth The maximum width of the texture. If the RawData
 *    is larger than this size it will be scaled down to this size. Note that
 *    DXT format textures can not be scaled. Default = 2048.
 * @param {number} opt_maxHeight The maximum width of the texture. If the
 *    RawData is larger than this size it will be scaled down to this size. Note
 *    that DXT format textures can not be scaled. Default = 2048.
 * @return {!o3d.Texture} The created texture.
 */
o3djs.texture.createTextureFromRawData = function(
    pack,
    rawData,
    opt_generateMips,
    opt_flip,
    opt_maxWidth,
    opt_maxHeight) {
  // Make a bitmaps from the raw data.
  var bitmaps = pack.createBitmapsFromRawData(rawData);
  if (opt_flip || typeof opt_flip === 'undefined') {
    for (var ii = 0; ii < bitmaps.length; ++ii) {
      var bitmap = bitmaps[ii];
      if (bitmap.semantic == o3djs.base.o3d.Bitmap.IMAGE) {
        bitmaps[ii].flipVertically();
      }
    }
  }

  // Create a texture from the bitmaps.
  var texture = o3djs.texture.createTextureFromBitmaps(
      pack, bitmaps, opt_generateMips);

  // Delete the bitmaps.
  for (var ii = 0; ii < bitmaps.length; ++ii) {
    pack.removeObject(bitmaps[ii]);
  }

  return texture;
};

/**
 * Creates a texture from an array of RawData objects. This is mainly useful for
 * creating a cube map out of six separate textures.
 * @param {!o3d.Pack} pack The pack to create the texture in.
 * @param {!Array.<!o3d.RawData>} rawDataArray The array of raw data objects to
 *    create the texture from. If these represent the six faces of a cube map,
 *    they must be in the order FACE_POSITIVE_X, FACE_NEGATIVE_X,
 *    FACE_POSITIVE_Y, FACE_NEGATIVE_Y, FACE_POSITIVE_Z, FACE_NEGATIVE_Z
 * @param {boolean} opt_generateMips Whether or not to generate mips. Note, mips
 *    can not be generated for DXT textures although they will be loaded if they
 *    exist in the RawData.
 * @param {boolean} opt_flip Whether or not to flip the texture. Most DCC tools
 *    Like Maya, Max, etc expect the textures to be flipped.  Note that only
 *    2D (image) textures will be flipped. Cube textures will not be flipped.
 *    Default = true.
 * @param {number} opt_maxWidth The maximum width of the texture. If the RawData
 *    is larger than this size it will be scaled down to this size. Note that
 *    DXT format textures can not be scaled. Default = 2048.
 * @param {number} opt_maxHeight The maximum width of the texture. If the
 *    RawData is larger than this size it will be scaled down to this size. Note
 *    that DXT format textures can not be scaled. Default = 2048.
 * @return {!o3d.Texture} The created texture.
 */
o3djs.texture.createTextureFromRawDataArray = function(
    pack,
    rawDataArray,
    opt_generateMips,
    opt_flip,
    opt_maxWidth,
    opt_maxHeight) {
  // Make bitmaps from the raw data.
  var bitmaps = [];
  for (var ii = 0; ii < rawDataArray.length; ++ii) {
    bitmaps = bitmaps.concat(pack.createBitmapsFromRawData(rawDataArray[ii]));
  }
  if (opt_flip || typeof opt_flip === 'undefined') {
    for (var ii = 0; ii < bitmaps.length; ++ii) {
      var bitmap = bitmaps[ii];
      if (bitmap.semantic == o3djs.base.o3d.Bitmap.IMAGE) {
        bitmaps[ii].flipVertically();
      }
    }
  }

  // Create a texture from the bitmaps.
  // TODO(kbr): use createCubeTextureFrom6Bitmaps instead; bugs in the plugin
  // currently prevent this.
  var texture = o3djs.texture.createTextureFromBitmaps(
      pack, bitmaps, opt_generateMips);

  // Delete the bitmaps.
  for (var ii = 0; ii < bitmaps.length; ++ii) {
    pack.removeObject(bitmaps[ii]);
  }

  return texture;
};

/**
 * Returns whether or not a given texture format can be scaled.
 * @param {!o3d.Texture.Format} format The format to check.
 * @return {boolean} True if you can scale and make mips for the given format.
 */
o3djs.texture.canMakeMipsAndScale = function(format) {
  switch (format) {
  case o3djs.base.o3d.Texture.XRGB8:
  case o3djs.base.o3d.Texture.ARGB8:
  case o3djs.base.o3d.Texture.ABGR16F:
  case o3djs.base.o3d.Texture.R32F:
  case o3djs.base.o3d.Texture.ABGR32F:
    return true;
  case o3djs.base.o3d.Texture.DXT1:
  case o3djs.base.o3d.Texture.DXT3:
  case o3djs.base.o3d.Texture.DXT5:
    return false;
  }
  return false;
};

/**
 * Creates a Texture from an array of bitmaps.
 * @param {!o3d.Pack} pack The pack to create the texture in.
 * @param {!Array.<!o3d.Bitmap>} bitmaps An array of bitmaps to create the
 *     texture from. For a 2D texture this would be 1 bitmap. For a cubemap this
 *     would be 6 bitmaps.
 * @param {boolean} opt_generateMips Whether or not to generate mips. Note, mips
 *    can not be generated for DXT textures although they will be loaded if they
 *    exist in the RawData. Default = true.
 * @return {!o3d.Texture} The created texture.
 */
o3djs.texture.createTextureFromBitmaps = function(
    pack,
    bitmaps,
    opt_generateMips) {
  if (bitmaps.length == 0) {
    throw 'no bitmaps';
  }

  var srcWidth = bitmaps[0].width;
  var srcHeight = bitmaps[0].height;
  var format = bitmaps[0].format;
  var mipMaps = bitmaps[0].numMipmaps;
  var maxMips = o3djs.texture.computeNumLevels(srcWidth, srcHeight);
  var targetMips = mipMaps;
  var dstWidth = srcWidth;
  var dstHeight = srcHeight;
  if ((typeof opt_generateMips === 'undefined' || opt_generateMips) &&
      o3djs.texture.canMakeMipsAndScale(format) &&
      mipMaps == 1 && maxMips > 1) {
    targetMips = maxMips;
  }

  // Check that all the bitmaps are the same size and make mips
  for (var ii = 0; ii < bitmaps.length; ++ii) {
    var bitmap = bitmaps[ii];
    if (bitmap.width != srcWidth ||
        bitmap.height != srcHeight ||
        bitmap.format != format ||
        bitmap.numMipmaps != mipMaps) {
      throw 'bitmaps must all be the same width, height, mips and format';
    }
    if (targetMips != mipMaps) {
      bitmap.generateMips(0, targetMips - 1);
    }
  }

  var levels = bitmap.numMipmaps > 1 ? bitmap.numMipmaps :
               o3djs.texture.computeNumLevels(dstWidth, dstHeight);
  var texture;
  if (bitmaps.length == 6 &&
      bitmaps[0].semantic != o3djs.base.o3d.Bitmap.SLICE) {
    if (srcWidth != srcHeight ||
        srcWidth != dstWidth ||
        srcHeight != dstHeight) {
      throw 'Cubemaps must be square';
    }
    texture = pack.createTextureCUBE(dstWidth, format, targetMips, false);
    for (var ii = 0; ii < 6; ++ii) {
      texture.setFromBitmap(
          /** @type {o3d.TextureCUBE.CubeFace} */ (ii),
          bitmaps[ii]);
    }
  } else if (bitmaps.length == 1) {
    texture = pack.createTexture2D(
        dstWidth, dstHeight, format, targetMips, false);
    texture.setFromBitmap(bitmaps[0]);
  }

  return /** @type{!o3d.Texture} */ (texture);
};

/**
 * Creates a TextureCUBE from 6 bitmaps. The bitmaps do not have to be the same
 * size though they do have to be the same format.
 *
 * @param {!o3d.Pack} pack The pack to create the texture in.
 * @param {number} edgeLength The size of the cubemap.
 * @param {!Array.<!o3d.Bitmap>} bitmaps An array of 6 bitmaps in the order
 *     FACE_POSITIVE_X, FACE_NEGATIVE_X, FACE_POSITIVE_Y, FACE_NEGATIVE_Y,
 *     FACE_POSITIVE_Z, FACE_NEGATIVE_Z.
 * @return {!o3d.Texture} The created texture.
 */
o3djs.texture.createCubeTextureFrom6Bitmaps = function(
    pack, edgeLength, bitmaps) {
  var numMips = o3djs.texture.computeNumLevels(edgeLength, edgeLength);
  var texture = pack.createTextureCUBE(
      edgeLength, bitmaps[0].format, numMips, false);
  for (var ii = 0; ii < 6; ++ii) {
    var bitmap = bitmaps[ii];
    texture.setFromBitmap(ii, bitmap);
  }
  texture.generateMips(0, numMips - 1);
  return texture;
};

/*
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview This file contains various functions for helping setup
 * shapes for o3d.  It puts them in the "shape" module on the o3djs
 * object.
 *
 *     Note: This library is only a sample. It is not meant to be some official
 *     library. It is provided only as example code.
 *
 */

o3djs.provide('o3djs.shape');

/**
 * A Module for shapes.
 * @namespace
 */
o3djs.shape = o3djs.shape || {};

/**
 * Adds missing tex coord streams to a shape's elements.
 * @param {!o3d.Shape} shape Shape to add missing streams to.
 * @see o3djs.element.addMissingTexCoordStreams
 */
o3djs.shape.addMissingTexCoordStreams = function(shape) {
  var elements = shape.elements;
  for (var ee = 0; ee < elements.length; ++ee) {
    var element = elements[ee];
    o3djs.element.addMissingTexCoordStreams(element);
  }
};

/**
 * Sets the bounding box and z sort points of a shape's elements.
 * @param {!o3d.Shape} shape Shape to set info on.
 */
o3djs.shape.setBoundingBoxesAndZSortPoints = function(shape) {
  var elements = shape.elements;
  for (var ee = 0; ee < elements.length; ++ee) {
    var element = elements[ee];
    o3djs.element.setBoundingBoxAndZSortPoint(element);
  }
};

/**
 * Prepares a shape by setting its boundingBox, zSortPoint and creating
 * DrawElements.
 * @param {!o3d.Pack} pack Pack to manage created objects.
 * @param {!o3d.Shape} shape Shape to prepare.
 */
o3djs.shape.prepareShape = function(pack, shape) {
  shape.createDrawElements(pack, null);
  o3djs.shape.setBoundingBoxesAndZSortPoints(shape);
  o3djs.shape.addMissingTexCoordStreams(shape);
};

/**
 * Prepares all the shapes in the given pack by setting their boundingBox,
 * zSortPoint and creating DrawElements.
 * @param {!o3d.Pack} pack Pack to manage created objects.
 */
o3djs.shape.prepareShapes = function(pack) {
  var shapes = pack.getObjectsByClassName('o3d.Shape');
  for (var ss = 0; ss < shapes.length; ++ss) {
    o3djs.shape.prepareShape(pack, shapes[ss]);
  }
};

/**
 * Attempts to delete the parts of a shape that were created by
 * duplicateShape as well as any drawElements attached to it.
 * @param {!o3d.Shape} shape shape to delete.
 * @param {!o3d.Pack} pack Pack to release objects from.
 */
o3djs.shape.deleteDuplicateShape = function(shape, pack) {
   var elements = shape.elements;
   for (var ee = 0; ee < elements.length; ee++) {
     var element = elements[ee];
     var drawElements = element.drawElements;
     for (var dd = 0; dd < drawElements.length; dd++) {
       var drawElement = drawElements[dd];
       pack.removeObject(drawElement);
     }
     pack.removeObject(element);
   }
   pack.removeObject(shape);
};

/**
 * Copies a shape's elements and streambank or buffers so the two will share
 * streambanks, vertex and index buffers.
 * @param {!o3d.Pack} pack Pack to manage created objects.
 * @param {!o3d.Shape} source The Shape to copy.
 * @return {!o3d.Shape} the new copy of the shape.
 */
o3djs.shape.duplicateShape = function(pack, source) {
  var newShape = pack.createObject('Shape');
  var elements = source.elements;
  for (var ee = 0; ee < elements.length; ee++) {
    var newElement = o3djs.element.duplicateElement(pack, elements[ee]);
    newElement.owner = newShape;
  }
  newShape.createDrawElements(pack, null);
  return newShape;
};

